一对多关系 | 青训营

184 阅读5分钟

多表关系中的一对多关系是十分常见的表关系。像老板与员工、班级与学生、用户 与文章等等。

一对多是最基础的表间关系,一张表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)

image.png

然而这样并不能获取到文章列表

所以这就需要使用预加载来家在文章列表:

var user User  
DB.Preload("Articles").Take(&user)  
fmt.Println(user)

image.png

预加载的参数就是外键关联的属性名。这样做的好处是,如果表十分复杂,就可以只获取需要查询的那一部分数据,用来节省性能。

同样的,如果查询某一个文章,是不显示用户信息的,也需要预加载:

var article Article  
DB.Preload("User").Take(&article)  
fmt.Println(article)

带条件的预加载

如果我们使用默认预加载显示某一个用户的文章列表,它会将所有的文章全部显示出来。我们可以使用带条件的预加载,选择某一部分的文章进行显示

假如我们只需要显示这个用户的 ID 大于 3 的文章,可以直接在 Preload 函数后面加上条件:

var user User  
DB.Preload("Articles", "id > ?", "3").Take(&user)

image.png 这样就只拿到了 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)