实践: 如何以GORM的风格使用GORM | 青训营

88 阅读4分钟

GORM是一个支持关联, 预加载等的全功能GORM. 然而, 尽管GORM的文档较为详细, 但是它几乎全部基于示例, 没有一定基础的用户很难彻底搞懂. 因此, 在实际使用中, 用户常常为了方便而仅仅把GORM作为原始SQL的链式函数写法, 甚至有人直接偷懒使用raw方法进行原始SQL一把梭, 将程序暴露在SQL注入的风险之中. 本文将用一个常用的示例来简单介绍如何尽量用GORM的, model的风格进行CRUD, 重点关注关联模式. 若有误或可优化之处, 还请指正. 首先当然要先放示例model:

type User struct {
	gorm.Model
	Username  string    `gorm:"uniqueIndex;size:32"`
	Password  string    `gorm:"size:64"` // bcrypt结果长度为60
	Signature string    `gorm:"size:256"`
	Works     []Video   `gorm:"foreignKey:AuthorID"`
	Favorites []*Video  `gorm:"many2many:favorite"`
	Comments  []Comment `gorm:"foreignKey:AuthorID"`
	Follows   []*User   `gorm:"many2many:follow;joinForeignKey:user_id;joinReferences:follow_id"`
	Followers []*User   `gorm:"many2many:follow;joinForeignKey:follow_id;joinReferences:user_id"`
	Messages  []Message `gorm:"foreignKey:FromUserID"`
}
type Video struct {
	gorm.Model
	Title     string `gorm:"size:256"`
	AuthorID  uint
	Favorited []*User   `gorm:"many2many:favorite"`
	Comments  []Comment `gorm:"foreignKey:VideoID"`
}

uniqueIndex等常见tag暂且不谈, 本文重点在于关联模式.

  1. 首先来看较为简单的一对多模式. 从user model中可见works项是一user对多video. 事实上, 上述model结构体中的列表字段皆不存在于实际的user数据表中!!! 一对多关系仅由video model中的AuthorID这一user的外键实现.
    • 在创建video并于user建立关联时便很简单, 当然可以采用GORM的关联创建模式, 但个人还是建议直接使用传统的create方法, 填好外键即可. (这样在comment这样同属于user和video两个model的复杂关系情况下, 只需执行一个方法便可实现创建.)
    • 更新, 查找和删除的重点实际上都是如何找到要操作的video, 因此只用一个示例就能够解释清楚:
    err = DB.Model(&model.User{Model: gorm.Model{ID: id}}).Select("id").Association("Works").Find(&videos)
    
    在上述命令/操作中, select的实际上是Works的id! 因为链式操作的末尾接了.Association("Works"). 若要使用where, 同样作用在Works上. 其它则不再赘述. 你可能好奇, 那么如何知道是从哪个user的Works中查找目标video呢? 答案就在.Model(&model.User{Model: gorm.Model{ID: id}})中. GORM官方提到过, 可以填写主键(默认为id)进行查找. 上述命令看似只有一句, 事实上GORM已经处理好了如何先找到目标user, 再在它的Works中查找目标video. 至于删改, 替换.Find()并自由结合where或主键查找到子model即可.
  2. 从属模式和一对一模式与1.类似, 重点都在于主键查找父model(.Model(xxx)). 同样的, 个人建议create时直接填写外键简单创建即可, 可以不必使用association()并append().
  3. 最难的部分来了, 多对多模式, 特别是自引用多对多模式. 可以看到上述user model中实现了关注(follower)与粉丝(follower)两个紧密结合的关系, 并只指定了同一张额外的表(follow)! 不用GORM的常规方式当然很好理解, 一张表内放两列即可实现, 但是GORM默认每个隐式书写的多对多都会创建一张连接表, 这是如何在GORM的风格下实现的呢? 原理其实很简单, 强制指定为同一张连接表后, GORM会听从指令, 但此时会很容易导致混乱: 因为默认自动生成两张表时, follow字段的外键为follow_id, follower为follower_id, 主体user在两张表内的外键都叫user_id. 在只生成一张表时, 谁对应谁呢? 答案就在joinForeignKey:user_id;joinReferences:follow_idtag中. 不要被名字所误导, joinForeignKeyjoinReferences都是连接表中的外键名字! 实际的对应关系见以上示例model. 同理, 此时的foreignKeyreferences同样具有误导性, 它们两个实际上都是连接表中外键对应的引用! 虽然上述示例model中不含这两个tag, 猜一猜谁是谁的外键, 谁又是谁的引用. 下面用GORM官方示例给出答案, 但要注意foreignKeyreferencestag并不一定要在本model对应的字段后, 要按实际情况而定, 比如下面的代码:
type User struct {
    gorm.Model
    Profiles []Profile `gorm:"many2many:user_profiles;foreignKey:Refer;joinForeignKey:UserReferID;References:UserRefer;joinReferences:ProfileRefer"`
    Refer    uint      `gorm:"index:,unique"`
}  
type Profile struct {
    gorm.Model
    Name      string
    UserRefer uint `gorm:"index:,unique"`
}

使用GORM风格进行CRUD, 尤其是关联模式的CRUD, 最大的好处就是只要注意官方指南中的几个方法, 就能轻松避免SQL注入, 并且因为逻辑清晰, 使复杂的查询获得更佳的可读性与有一定可能会更好的性能.