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