Gorm 是一个 Go 语言的 ORM,功能比较强大,招新平台的开发就用到了这个包。
关联模式是各类 ORM 难以回避的话题,也是数据库设计必须考虑的东西,所以开个坑记录一下。
目前这篇文章只有预加载部分。
四种关联模式
这个坑不一定会填,但是很重要,所以在这里保存一些有用的资料,另外看时间补充一些自己的理解。
文档:实体关联 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
貌似关联模式是借鉴的 Ruby 里面的概念:Active Record Associations — Ruby on Rails Guides
belong_to
和 has_one
搞不清楚:Ruby on Rails : Has_One versus Belongs_To (duanesbrain.blogspot.com)
预加载
预加载包括两种方法:Joins
和 Preload
.
前者类似于 SQL 的 JOIN
命令,在实体关联中,仅适用于一对一的关系,例如 belong_to
和 has_one
。而后者则是 GORM 引入的概念,主要作用是在处理某个 Model 时将关联的 Model 中的信息预先读取出来。
关于 Joins
适用范围较小也比较简单,这里不再讨论,下面主要讲一下 Preload
。
举个例子:
type User struct {
*gorm.Model
Username string `json:"username"`
Password string `json:"password"`
Type string `json:"type"`
}
type Score struct {
*gorm.Model
UserId uint `json:"user_id"` // Foreign Key
User User `gorm:"foreignKey:UserId"`
Total int `json:"total"`
Web int `json:"web"`
Bin int `json:"bin"`
Pwn int `json:"pwn"`
Crypto int `json:"crypto"`
Misc int `json:"misc"`
}
这是一个 Belong_to
的关系,即每个 Score 都属于一个 User,换句话说就是每个 User 都有一个 Score。
Preload
基础
假设我正常查询某个 Score 时:
var score Score
DB.Model(Score{}).Take(&Score)
这样取出的 score 里面不会有 User 的信息,也就是说如果我试图通过 score.User.Username
获取相关联的 User 的 Username
,得到的只会是 ""
,原因是数据库里面 Score 表没有保存 User 表的信息,只有一个用于关联的外键 user_id
,所以通过一次查询 Score 不可能把 User 的信息取出来。
如果只是偶尔取出一两个关联的字段还可以手动实现,如果实体关联比较复杂那么我们需要使用 Preload
处理,请看例子:
var ScoreList []Score
// 假设当前 ScoreList 中有 4 条记录,其中 user_id 从 1 到 4。
db.Preload("User").Find(&ScoreList)
// 等价于:
// SELECT * FROM users;
// SELECT * FROM scores WHERE user_id IN (1,2,3,4);
在两个 SELECT
执行完毕后,第二条(预加载)指令运行后的结果还会被写入到 ScoreList
中。此时我们调用 ScoreList[0].User.Username
就可以获取到第 1 条记录对应的 User 的信息。
这是最基础的 Preload
用法,接下来我们可以进行拓展。
Preload
高级用法
前三种都很好理解,建议直接点上面链接看官方文档。
最后一种需要注意,在构造匿名函数的时候一定要返回外键(也就是上例中的 UserId
),举个例子:
var ScoreList []Score
DB.Preload("User", func(DB *gorm.DB) *gorm.DB {
return DB.Select("username, type")
}).Find(&ScoreList)
乍一看,这段代码可以实现返回的 ScoreList
中只有 User 的 username
、type
,而阻止了 password
这种敏感信息的返回。
但是运行之后会发现 ScoreList
中的 User 仍为空,和没有预加载的效果是一致的。
原因是,Preload
需要依靠外键进行实体之间的匹配,所以上面第三行应该改为:
return DB.Select("id, username, type"),
最后重复一下:只有把 id
(上例中 Score 的外键)也返回才能实现预加载,否则只是简单 SELECT
一下,没有实际效果!