Golang GORM实战(三) | 数据模型

2022年4月18日 285点热度 0人点赞 0条评论

这是《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大小写不敏感,如primarykeyprimaryKey是一样的,不过还是推荐按下表列出的名称去使用。

标签名(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操作数据表。

13740Golang GORM实战(三) | 数据模型

root

这个人很懒,什么都没留下

文章评论