首先准备两个结构体:学生和老师

1
2
3
4
5
6
7
8
9
type Student struct {
Id int
Name string
}

type Teacher struct {
Id int
Name string
}

然后自动建表

1
2
3
func One2one() {
GLOBAL_DB.AutoMigrate(&Student{}, &Teacher{})
}

image-20221005101220168

你会发现两张表都能被建出来,但是相互没有关联

下面来创建一对一关联

PS:Belongs To 和 Has One 在数据库是一样的,只是本地的代码结构不同


Belongs To(属于)

要说明某个学生属于某个老师,可以在 Student 中加上一个老师的 Id 和一个实例

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

// belongs to
TeacherId int
Teacher Teacher
}

type Teacher struct {
Id int
Name string
}

这时就只需要用 Student 来建表了,清空数据库并重新建表

1
2
3
func One2one() {
GLOBAL_DB.AutoMigrate(&Student{})
}

image-20221005102027649

你可以看见, GORM 自动将学生和老师都建好了,并且学生有一个 teacher_id 来记录是哪个老师的学生

这就创建了属于关系

下面来构造并插入数据(注意主体,插入的是学生)

1
2
3
4
5
6
// create a teacher
teacher := Teacher{Name: "teacher1"}
// create a student,不需要给出老师 Id
student := Student{Name: "student1", Teacher: teacher}

GLOBAL_DB.Create(&student)

image-20221005102714347

可以看见被成功地插入


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
}

不同的是,现在需要传入两个结构建表

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

image-20221005111210057

现在先创建一个老师,这次尝试不指定学生

1
2
teacher1 := Teacher{Name: "teacher1"}
GLOBAL_DB.Create(&teacher1)

image-20221005111808856

可见就只插入了一个老师

现在再准备一对师生

1
2
3
4
5
6
// create student
student := Student{Name: "student1"}

// create a teacher
teacher2 := Teacher{Name: "teacher2", Student: student}
GLOBAL_DB.Create(&teacher2)

image-20221005111844588

可以看见学生自动指向了老师


预加载

接下来讲查询

如果查询时要带出本身关联的其他结构的话,需要使用预加载

就接着上面的 has one 的例子,尝试查询刚才插入的那一对师生

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

但是你会发现只能查到老师

image-20221005113014291

加上预加载方法 .Preload("Student") ,就能一并拿到学生

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

image-20221005113136742

只要是确立了关联,就能使用预加载,下面尝试使用 belongs to 关联

因为数据库上其实是一样的,就只用改本地代码

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

// belongs to
TeacherId int
Teacher Teacher
}

type Teacher struct {
Id int
Name string
}

func One2one() {
var result Student
GLOBAL_DB.Preload("Teacher").First(&result, 1)
fmt.Println(result)
}

image-20221005113947043

这就是 has one 和 belongs to 的预加载查询


关系操作

上面在是在插入的时候成对插入的,插入时就有了关系

但是如果你在插入的时候只插入了一半,然后后面另一半是后来单独插入的,现在要手动把这个关系补上

或者想手动编辑或删除关系,就需要进行关系操作

创建 Append()

从上面的状态继续,但是现在只留一对师生,而且把他们的关系清除掉,尝试手动创建关系

image-20221005120935754

使用 Association() 方法即可,但是要搞清楚关系主体,这里是学生属于老师,所以 Model() 里面传的是学生

1
2
3
4
s := Student{Id: 1}
t := Teacher{Id: 1}

GLOBAL_DB.Model(&s).Association("Teacher").Append(&t)

image-20221005121821210

可见关系已经创建

替换 Replace()

新加一个老师,然后尝试将关系替换为第二个

image-20221005123235854

1
GLOBAL_DB.Model(&Student{Id: 1}).Association("Teacher").Replace(&Teacher{Id: 2})

image-20221005123401174

删除 Delete()

这个也很好理解

1
GLOBAL_DB.Model(&Student{Id: 1}).Association("Teacher").Delete(&Teacher{Id: 2})

image-20221005123437496

清空 Clear()

为什么有个删除还有个清空呢?因为后面还有一对多与多对多关系,删除一次只能删一个,清空可以全删了

计数 Count()

这个肯定也是放到后面讲了

小小的总结

你现在能说清楚 Belongs To 和 Has One 了嘛

A Belongs To B:

  • 在 A 中有一个 B 的实例
  • A 中有 B 的编号

B Has One A:

  • 在 B 中有一个 A 的实例
  • A 中有 B 的编号

其实只要 A 中有 B 的编号就已经建立关系了

里面的实例是为了插入/查询数据的时候方便,用一个结构体就行了

也就是说,这两个其实是一个东西,只是表达方式不同(看以谁为主语)