Has Many(一对多)

官网地址:https://gorm.io/zh_CN/docs/has_many.html

先回忆一下 Has One 的表达

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Student struct {
Id int
Name string

// has one
TeacherID int
}

type Teacher struct {
Id int
Name string

// has one
Student Student
}

然后现在是 Has Many 嘛,一个 teacher 必定不止有一个 student

所以 Student 应该是一个切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Student struct {
Id int
Name string

TeacherID int
}

type Teacher struct {
Id int
Name string

// has many
Student []Student
}

然后创建表

1
GLOBAL_DB.AutoMigrate(&Student{}, &Teacher{})

image-20221006224001738

可以看见,数据库中的结构与 Has One 的是一样的

也不难理解,Has Many 只是能让多个学生指向同一个老师而已


创建

现在尝试一下使用 Has Many 的创建

1
2
3
4
5
6
7
8
9
10
11
12
student1 := Student{Name: "student1"}
student2 := Student{Name: "student2"}

teacher1 := Teacher{
Name: "teacher1",
Student: []Student{
student1,
student2,
},
}

GLOBAL_DB.Create(&teacher1)

image-20221006224550621

可以看见两个学生都指向了同一个老师,这就是 Has Many


查询

如果你直接查询老师的话,你会发现没有学生的信息

1
2
3
var result Teacher
GLOBAL_DB.First(&result)
fmt.Println(result)

image-20221006224901598

为什么呢?因为没有预加载

1
GLOBAL_DB.Preload("Student").First(&result)

这样就可以了

image-20221006225006528


预加载的进阶使用

官网地址:https://gorm.io/zh_CN/docs/preload.html

条件预加载

那么如果需要筛选一下呢?比如只想带出第一个学生

可以直接在预加载函数中添加条件

1
GLOBAL_DB.Preload("Student", "name = ?", "student1").First(&result)

image-20221006225421607


自定义预加载 SQL

这只是简单场景,如果是要复杂的过滤呢?

或者像官网上一样搞排序什么的(

那就要使用自定义的函数了

1
2
3
GLOBAL_DB.Preload("Student", func(db *gorm.DB) *gorm.DB {
return db.Where("name = ?", "student1")
}).First(&result)

这个函数里面可以塞很多东西


链式预加载

关系很可能不止一层,现在给学生拥有一个 Info 模型,里面有学生的成绩

(当然这只是为了举例子,别杠为什么不直接写在 Student 里面)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type Info struct {
Id int
Score int

StudentID int
}

type Student struct {
Id int
Name string
Info Info

TeacherID int
}

type Teacher struct {
Id int
Name string

// has many
Student []Student
}

然后创建

1
GLOBAL_DB.AutoMigrate(&Student{}, &Teacher{}, &Info{})

这里为了省事就直接往里面塞数据了

image-20221006233340058

尝试刚才的查询,你会发现成绩并没有被带出来

1
2
3
var result Teacher
GLOBAL_DB.Preload("Student").First(&result)
fmt.Println(result)

image-20221006233440504

应该使用链式结构

1
GLOBAL_DB.Preload("Student.Info").First(&result)

但是这里就有一个问题需要说明,就是要怎么添加条件

例如条件还是和上面一样,只要第一个学生,正确的写法应当是这样

1
GLOBAL_DB.Preload("Student.Info").Preload("Student", "name = ?", "student1").First(&result)

也就是说,每层的预加载只能决定你当前层的数据能不能被带出来

例如这样

1
GLOBAL_DB.Preload("Student.Info", "score > 60").First(&result)

image-20221007000202735

你会看见两个人都被查出来了,但是不及格的成绩没有被带出来

查询条件只适用于当前加载的那一层


Joins() 预加载

上面的那条看上去只会查到及格的学生,但其实并不是

那如果我就想要及格的学生,没及格的我看都不想看见呢?

这可以使用自定义函数 + Joins()

1
2
3
GLOBAL_DB.Preload("Student", func(db *gorm.DB) *gorm.DB {
return db.Joins("Info").Where("score > ?", 60)
}).First(&result)

image-20221007001010455

这东西常用的就是这样,详细的可以去官网看看