Go语言框架之GORM使用(三) | 青训营

214 阅读5分钟

GORM的使用入门

触发器(Hook)

Hook 是在创建、查询、更新、删除等操作之前、之后调用的函数。

假如说,我们想在一条记录插入到数据库之前,先打印出插入的内容

type User struct {
  Name   string 
  Age    int   
  Address  *string 
}

func (user *User) BeforeCreate(tx *gorm.DB) (err error) {
  address := fmt.Sprintf("%s' home", user.Address)
  user.Address = &address
  return nil
}

可用的触发器

  • BeforeCreate 在插入记录之前
  • AfterCreate 在插入记录之后
  • BeforeUpdate 在更新记录之前
  • AfterUpdate 在更新记录之后
  • BeforeSave 在保存记录之前
  • AfterSave 在保存记录之后
  • BeforeDelete 在删除记录之前
  • AfterDelete 在删除记录之后
  • AfterFind/AfterFirst/AfterTake/AfterLast 在查询记录之后

注意:在触发器运行出错报错之后,如果是对数据库造成修改的操作,会产生事务性的回滚;查询等不会影响数据库的操作,便不会触发回滚。

外键

一对多关系

一对多关系在现实中的应用很常见,例如关注列表,视频发布列表等等都是一对多的关系。Gorm中,一对多关系有两类

  • Belongs To 属于
  • Has Many 拥有

我们以用户发布的视频列表为例,一个用户可以发布多个视频,但每个视频只能拥有一个作者。

type User struct {
  ID       uint     `
  Name     string    
  Videos   []Video    // 用户发布的视频
}

type Article struct {
  ID     uint   
  Title  string 
  UserID uint
  User   User
}

上面的代码片段每一个Article就属于一个User,而UserID便是外键 注意:被引用的类型要和引用的外键类型一致,包括大小。外键名称就是关联表名加上引用者的类型名。

如果想自定义外键,也可以对外键进行重写,可以使用gorm:"foreignKey:xxx"进行自定义外键,如

type User struct {
  ID       uint      
  Name     string    
  Videos   []Video `gorm:"foreignKey:UID"`// has many关系
}

type Video struct {
  ID       uint   
  Title    string 
  UID      uint
  User     User    `gorm:"foreignKey:UID"` // belongs to关系
}

Gorm通一般使用被引用者的主字段作为外键的参照,如果想修改,Gorm也提供了独特的字段帮助你进行修改。gorm:"references:xxx"

type User struct {
  ID       uint
  Name     string
  Videos   []Video `gorm:"foreignKey:UserName;references:Name"` // has many关系
}

type Video struct {
  ID       uint  
  Title    string 
  UserName string
  User     User `gorm:"references:Name"` // belongs to关系
}
插入记录

如果需要创建用户的时候,同时创建他发布的视频。

v1 := Video{Title: "xxx1"}
v2 := Video{Title: "xxx2"}
user := User{Name: "xxx", Videos: []Video{v1, v2}}
DB.Create(&user)

Gorm会自动将他们关联。

如果用户已存在,需要添加视频,并且关联已有的用户。

v1 := Video{Title: "xxx1", UserID: 3}
DB.Create(&v1)

或者

var user User
DB.Take(&user, 3)
DB.Create(&Article{Title: "xxx1", User: user})

如果用户和视频都已经存在,需要添加外键,可以使用Save方法或者Append方法。

查询记录

如果想查询用户,并显示其视频列表,直接用Take/First/Last方法是不行的。必须要使用Gorm提供的预加载方法来加载文章列表Preload

var user User
DB.Preload("Videos").Take(&user, 3)

其中,预加载的名称就是外键关联的属性名。 同理,如果需要查询视频,并显示它的作者信息,也需要使用预加载功能。

var video Video
DB.Preload("User").Take(&video, 4)

更高级的用法是:如果需要查询视频显示其作者信息,并且显示作者所有的视频,则需要嵌套预加载。如 DB.Preload("User.Videos").Take(&video, 3)。也可以在预加载中附带条件信息,如必须要满足视频ID小于300,DB.Preload("Articles", "id < ?", 300).Take(&user, 1)。

删除记录

删除用户的同时如果同时删除名下所有视频,即级联删除,可使用DB.Select("Videos").Delete(&user)。如果只是想删除用户,而只将其视频的外键置为空。

DB.Preload("Videos").Take(&user, 3) 
DB.Model(&user).Association("Videos").Delete(&user.Videos)

一对一关系

一对一关系使用的场景一般较少,比较常见的应用场景是用于表的拓展。比如说,我们可以将一个用户表拆分成两个部分,常常需要查询修改的部分放在主表当中,长时间不需要变动的部分放置在副表当中,这样便可以提高查询效率。

type User struct {
  ID       uint
  Name     string
  Age      int
  UserInfo UserInfo // 通过UserInfo可以拿到用户详情信息
}

type UserInfo struct {
  UserID uint // 外键
  ID     uint
  Addr   string
  Like   string
}
插入记录

假如说我们定义了一对一关系之后,插入员工记录

DB.Create(&User{
  Name:   "xxx",
  Age:    3,
  UserInfo: UserInfo{
    Addr: "Beijing",
    Like: "sleep",
  },
})

也可以通过修改结构体结构进行副表的添加。例如修改副表

type UserInfo struct {
  User   *User
  UserID uint
  ID     uint
  Addr   string
  Like   string
}

注意:副表需要传递指针,否则会因为嵌套引用而发生报错。修改成这样之后,就可以通过下列方式进行记录的添加

var user User
DB.Take(&user, 5)
DB.Create(&UserInfo{
  User: &user,
  Addr: "Bejing",
  Like: "sleep",
})

和一对多关系类似,我们也可以通过在gorm控制标签中添加标签来重写外键和引用。

多对多关系

假设每个用户都可以发布多个视频,视频也可以多个用户联合发布,则用户和视频可以构成多对多关系。

type User struct {
  ID       uint
  Name     string
  Videos []Video `gorm:"many2many:video_users;"`
}

type Video struct {
  ID    uint
  Title string
  Users  []User `gorm:"many2many:video_users;"`
}

两个结构体都有标签适用于反向引用。

插入记录

DB.Create(&Video{
  Title: "xxx1",
  Users: []User{
    {Name: "xxx"},
    {Name: "yyy"},
  },
})

如果需要自定义外键,操作方法和一对多的方法差不多。