这是《Golang GORM实战》系列的第三篇,在这篇文章中我们来聊一聊GORM数据模型的一些细节。
模型定义
GORM的数据模型是一个标准的Go struct,一个数据模型代表一张数据表,模型由基本数据类型和实现了Scanner和Valuer接口的自定义类型及其指针或者别名组成,如:
type User struct {
ID uint
Name string
Email *string
Age uint8
Birthday *time.Time
MemberNumber sql.NullString
ActivatedAt sql.NullTime
CreatedAt time.Time
UpdatedAt time.Time
}
gorm.Model
对于一张数据表的每一条数据来说,都有自增主键,创建、更新与删除时间等比较通用的字段,因此GORM将其抽出来并定义了gorm.Model,如:
// gorm.Model 的定义
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
如果有需要,我们可以将gorm.Model嵌入到我们的模型中,而不用重复定义那几个字段,如:
type User struct{
gorm.Model
Name string
}
上面嵌入gorm.Model的User等同于下面的User:
type User struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
Name string
}
嵌入结构体
上面我们已经在自己定义的结构体中嵌入gorm.Model,当然,我们也可以嵌入自定义的结构,匿名嵌入,这种方式与上面嵌入gorm.Model相同,如:
type User struct{
Username string
Age int
}
type House struct{
ID int
Name string
User
}
//等同于
type House struct{
ID int
Name string
Username string
Age int
}
而非匿名嵌入,也就是正常的结构体字段,则需要声明字段标签embedded
才可以嵌入,否则会被GORM当作关联模型处理,但于未定义关联关系,所以会报错,如:
//错误
type House struct{
ID int
Name string
User User
}
//正确
type House struct{
ID int
Name string
User User `gorm:"embedded"`
}
另外,无论是正常结构体嵌入还是匿名嵌入,都可以使用字段标签embeddedPrefix
来指定嵌入结构全部字段的前缀,如:
type House struct{
ID int
Name string
User User `gorm:"embedded;embeddedPrefix:house_"`
}
//等同于
type House struct{
ID int
Name string
HouseUsername string
HouserAge int
}
主键ID
GORM默认会把ID作为数据表的主键,如:
type User struct {
ID string // 默认情况下,名为 `ID` 的字段会作为表的主键
Name string
}
如果ID是整型且为自增字段的话,则在新增时,会自动填充该值,如:
//ID此时为0
var u = &User{Name:"test"}
//创建成功后,此时ID自动填充为数据表自增字段的值
db.Create(u)
如果想设置其他字段为主键的话,可以通过字段标签primaryKey
来指定,如:
type User struct {
UserID string `gorm:"primaryKey"`
Name string
}
另外,也可以将多个字段设置为字段,而多个字段组成的主键也叫复合主键,如:
type Student struct{
StudentID uint64 `gorm:"primaryKey"`
ClassId uint64 `gorm:"primaryKey"`
}
无论是ID,还是通过primaryKey设置的单字段主键或联合主键,如果字段的数据类型为整型,GORM都会把该字段设置为自增字段(AUTO_INCREMENT),可以通过autoIncrement
设置为false来禁用这个功能,如:
type Student struct{
StudentID uint64 `gorm:"primaryKey;autoIncrement:false"`
ClassId uint64 `gorm:"primaryKey;autoIncrement:false"`
}
时间追踪
GORM约定使用CreatedAt,Updatedat,DeletedAt追踪记录创建,更新,软删除的时间,如果在数据模型中定义了这几个字段,则在创建、更新、软删除记录时,会自动填充时间。
CreatedAt
如果数据模型有CreatedAt字段时,在创建记录时,如果没有指CreatedAt的值,则会将字段的值设置为当前时间。
db.Create(&user) // 将 `CreatedAt` 设为当前时间
也可以自己指定
user2 := User{Name: "jinzhu", CreatedAt: time.Now()}
db.Create(&user2) // user2 的 `CreatedAt` 不会被修改
也可以手动修改
db.Model(&user).Update("CreatedAt", time.Now())
UpdatedAt
//Update方法与UpdateColumn的区别
db.Model(&user).Update("name", "jinzhu") // 会将 `UpdatedAt` 设为当前时间
db.Model(&user).UpdateColumn("name", "jinzhu") // `UpdatedAt` 不会被修改
//对于Create方法来说,指定UpdatedAt就不更新,未指定则更新
user2 := User{Name: "jinzhu", UpdatedAt: time.Now()}
db.Create(&user2) // 创建记录时,user2 的 `UpdatedAt` 不会被修改
//对于Save方法来说,都更新
db.Save(&user) // 将 `UpdatedAt` 设为当前时间
user3 := User{Name: "jinzhu", UpdatedAt: time.Now()}
db.Save(&user3) // 更新时,user3 的 `UpdatedAt` 会修改为当前时间
DeletedAt
DeletedAt字段在使用Delete方法
删除数据表记录会起作用,具体我们在系列的其他文章讲解到删除记录会涉及到。
覆盖表名
GORM使用结构体的蛇形命名
作为数据表名称,如,对于User结构体来说,其表名为users,而GameUser结构的表名为game_users。
不过,如果不想按GORM的规则来指定数据表名,我们可以让数据模型实现Tabler接口,该接口的定义如下:
type Tabler interface {
TableName() string
}
让User结构体实现Tabler接口,如:
//表名为user,而不是users
func (u *User)TableName()string(){
return "user"
}
当然,除了定义模型的TableName方法外,也可以使用Scope动态生成表名,或者在执行GORM的Create, First, Find, Take, Save, Update, Delete等方法时,临时指定数据表名,当然如果你想指定模型的表名的话,定义模型的TableName方法是最简单,因此推荐通过这种方法来指定表名。
覆盖列名
数据表的列名使用的也是数据模型的蛇形命名
,如:
type User struct{
ID int //列名是id
name string //列名是name
}
不过,可以使用column标签来覆盖列表,如:
type User struct{
ID INT `gorm:"column:user_id"` //列名为user_id
}
标签(tag)
字段标签
对于数据模型(model)来说,字段标签并不是必须的,而是可选的,tag大小写不敏感,如primarykey
和primaryKey
是一样的,不过还是推荐按下表列出的名称去使用。
标签名(tag) | 标签说明 |
---|---|
column | 指定数据表列名 |
type | 列数据类型 |
size | 指定列的大小 |
primaryKey | 指定列为主键,默认ID为主键 |
unique | 指定列为唯一 |
default | 指定默认值 |
precision | 指定列的精度 |
scale | 指定列大小 |
not null | 指定列为 NOT NULL |
autoIncrement | 指定列为自动增长 |
autoIncrementIncrement | 自动步长,控制连续记录之间的间隔 |
embedded | 嵌套字段 |
embeddedPrefix | 嵌入字段的列名前缀 |
autoCreateTime | 创建时追踪当前时间,对于 int 字段,它会追踪秒级时间戳,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoCreateTime:nano |
autoUpdateTime | 创建/更新时追踪当前时间,对于 int 字段,它会追踪秒级时间戳,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoUpdateTime:milli |
index | 根据参数创建索引,多个字段使用相同的名称则创建复合索引,查看 索引 获取详情 |
uniqueIndex | 与 index 相同,但创建的是唯一索引 |
check | 创建检查约束,例如 check:age > 13,查看 约束 获取详情 |
<- | 设置字段写入的权限, <-:create 只创建、<-:update 只更新、<-:false 无写入权限、<- 创建和更新权限 |
-> | 设置字段读的权限,->:false 无读权限 |
- | 忽略此字段 |
comment | 创建表时字段的注释 |
上述的字段标签有很大一部分是用于迁移数据表的,不过这里不推荐使用GORM的数据迁移功能,这是因为对数据表的创建、删除、修改都是比较重大的变更和高危操作,因此需要严格评估后再实施操作,而不应该写在程序代码里。
而对于上述的标签的说明,有个大概印象就好,如需使用,再查看文档即可。
关联标签
另外,下面列出来的字段标签与数据模型的关联相关的,我们在之后的文章再行讲解。
标签名(tag) | 标签说明 |
---|---|
foreignKey | 指定当前模型的列作为连接表的外键 |
references | 指定引用表的列名,其将被映射为连接表外键 |
polymorphic | 指定多态类型,比如模型名 |
polymorphicValue | 指定多态值、默认表名 |
many2many | 指定连接表表名 |
joinForeignKey | 指定连接表的外键列名,其将被映射到当前表 |
joinReferences | 指定连接表的外键列名,其将被映射到引用表 |
constraint | 关系约束,例如:OnUpdate、OnDelete |
小结
对于GORM来说,数据模型其实是就是Go struct,对应一个数据库的数据表,GORM约定了许多数据模型到数据表的映射规则,比如表名与列名的蛇形复数命命规则,默认ID字段为主键等,但我们仍然可以通过对数据模型方法和标签的定义来改变这些约定,比如通过TableName方法来改变模型对应的数据表等,对GORM数据模型的理解,可以让我们更好地去使用GORM操作数据表。
文章评论