多表关系中的一对多关系是十分常见的表关系。像老板与员工、班级与学生、用户 与文章等等。
一对多是最基础的表间关系,一张表A中的一条记录可以对应另一张表B中的多条记录,另一张表B中的一条记录只能对应一张表A中的一条记录
一对多关系的表结构建立
以用户和文章为例子:一个用户可以发很多个文章,但是一篇文章只属于一个用户。
// Article 文章表,一篇文章属于一个用户
type Article struct {
ID uint `gorm:"size:4"`
Title string `gorm:"size:8"`
UserID uint `gorm:"size:4"` //属于 ,这里的类型要和引用的外键类型一致
User User //属于 belongs to
}
// User 用户表,一个用户拥有很多文章
type User struct {
ID uint `gorm:"size:4"`
Name string `gorm:"size:8"`
Articles []Article //用户拥有的文章列表 has many
}
在这里,一个用户可以有许多文章,所以用户下有一个文章列表(has many)。
在文章表中,一张表只对应一个用户(belongs to)。
还有一个外键,默认情况下,GORM 使用持有关联属性的 类型名 + 主键 ID 作为外键名。比如这里使用 UserID 作为外键名,外键的数据类型和长度都要与主键 ID 一致。
重写外键关联
我们可以修改默认的外键名称,但是修改完之后需要使用标签进行关联。
type User struct {
ID uint `gorm:"size:4"`
Name string `gorm:"size:8"`
Articles []Article `gorm:"foreignKey:UID"` // 用户拥有的文章列表
}
type Article struct {
ID uint `gorm:"size:4"`
Title string `gorm:"size:16"`
UID uint `gorm:"size:4"` // 属于
User User `gorm:"foreignKey:UID"` // 属于
}
在这里将外键名成改为了 UID,那么 User 这个外键关系就要指向 UID。与此同时,User 所拥有的 Articles 也得更改外键,改为 UID。
一对多的添加
用户文章同时创建
a1, a2 := Article{Title: "语文"}, Article{Title: "数学"}
user := User{
Name: "cfd",
Articles: []Article{a1, a2},
}
DB.Create(&user)
在这种情况下,像外键和主键 ID 什么的就不需要自己添加。我们将创建好的两篇文章放进文章列表中,程序会自动在两张表创建好数据并关联好外键。
创建文章关联已有的用户
在这里有两种方式来进行关联:
- 直接给外键赋值来进行关联
- 找到相关联的用户数据,赋值给 User 成员
对于第一种:
DB.Create(&Article{
Title: "英语",
UserID: 1,
})
第二种:
var user User
DB.Take(&user, 1) //找到ID为1的用户信息
DB.Create(&Article{
Title: "美术",
User: user,
})
给用户绑定一个文章
这里是用户和文章都有了,然后将这篇文章的外键设置为这个用户,表示这篇文章属于这个用户。
我们先分别找到用户和文章的数据:
var user User
DB.Take(&user, 1)
var article Article
DB.Take(&article, 6)
然后执行一个关联操作,将 article 添加到 user 的 Articles 关联中:
DB.Model(&user).Association("Articles").Append(&article)
DB.Model(&user)是选择要操作的模型,其中&user是对要操作的模型实例的引用。.Association("Articles")选择了模型中名为Articles的关联。.Append(&article)将article添加到user的Articles关联中。
给文章绑定一个用户
与上面类似,只不过主语换了。
var user User
DB.Take(&user, 1)
var article Article
DB.Take(&article, 6)
DB.Model(&article).Association("User").Append(&user)
将 user 添加到 article 的关联关系中
一对多的查询
默认预加载
如果我们需要显示某个用户的数据,那就由三个部分组成:ID、姓名、文章列表。
正常情况下是这样查询:
var user User
DB.Take(&user)
fmt.Println(user)
然而这样并不能获取到文章列表
所以这就需要使用预加载来家在文章列表:
var user User
DB.Preload("Articles").Take(&user)
fmt.Println(user)
预加载的参数就是外键关联的属性名。这样做的好处是,如果表十分复杂,就可以只获取需要查询的那一部分数据,用来节省性能。
同样的,如果查询某一个文章,是不显示用户信息的,也需要预加载:
var article Article
DB.Preload("User").Take(&article)
fmt.Println(article)
带条件的预加载
如果我们使用默认预加载显示某一个用户的文章列表,它会将所有的文章全部显示出来。我们可以使用带条件的预加载,选择某一部分的文章进行显示。
假如我们只需要显示这个用户的 ID 大于 3 的文章,可以直接在 Preload 函数后面加上条件:
var user User
DB.Preload("Articles", "id > ?", "3").Take(&user)
这样就只拿到了 ID 大于 3 的文章
Preload 函数同时也支持自定义条件,也就是 Preload 函数可以传一个自定义函数:
var user User
DB.Preload("Articles", func(db *gorm.DB) *gorm.DB {
return DB.Where("id > ?", 3)
}).Take(&user)
在 Preload 函数中的第二个参数就是一个自定义函数,同样是只显示 ID 大于 3 的文章。
删除
清除外键关系
在这种情况下,删除某一个用户的时候,只是将与用户有关联的文章的外键设置为 NULL
var user User
DB.Preload("Articles").Take(&user, 1)
DB.Model(&user).Association("Articles").Delete(&user.Articles)
DB.Delete(&user)
首先需要预加载将这个用户的文章列表加载出来,然后将所有文章的外键置空(删除掉文章和用户的联系),最后删除掉这个用户。
级联删除
级联删除表示在删除用户的同时将用户的所有文章也一并删除。
var user User
DB.Take(&user, 1)
DB.Select("Articles").Delete(&user)