重新学习关联
之前写了一篇关于关联的博客,当时是跟着B站的视频学的,但是学完后感觉对关联的理解,特别是表与表之间的关系,CRUD的操作还不是特别数量,期间写项目的时候总怪怪的,因此回来重新学习一下,这次完全就是跟着GORM文档学习,本篇博客不会描述具体怎么写代码,就记录一下我对这些关联的认识
Belongs To | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
开始之前
先铺垫一下外键,某个结构体A中有xxxID,则xxxID为外键,同时在图表中表示A指向xxx
A指向xxx,所以xxx不能随意修改删除
Belongs To#
属于#
记住**”属于”**这两个字,对理解这个关联挺有帮助的
个人感觉其实Belongs To并不能单纯地被认为是一对一关系,他的一对一关系指的是某个用户属于某个公司,但是从某个角度来看,其实也有很多用户属于这个公司,因此公司与每个员工来说是一对一的,结合起来确实一对多的
1 | type User struct { |
由此可见虽然是User属于company,但是却是user指向company,跟我们传统的思想不一样,所以这个是反向思维的,不如直接按照刚才外键方法理解
User结构体是包含公司结构体的,使用 BelongsTo
可以让你在查询用户时自动填充所属的公司信息(需要使用Preload),同时也表示我们在创建User的时候需要填充所属公司信息
不如带入到个人与公司这个环境中,你辞职了公式不会倒闭,但是公司倒闭了你肯定已经辞职了,按照这个逻辑就可以清楚解释为什么我们可以删除user而对应的公司不删除,我们删除公司的时候总是需要先将有关联的员工删除掉再注销公司
CRUD
实体关联 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
具体写法我就不赘述了
增加
创建User的时候需要填充所属公司信息
删除
删除User并不会直接把Company删除,毕竟只是一个员工辞职而已
更新
个人觉得正常人应该不会去修改关联,因此平常更新也就是平常的数据库操作更新,也就是与关联无关的字段的更新(上面的链接中也有介绍怎么修改关联)
查找
使用preload,记住要沿着箭头去查询,要不然就会变成
1
2User: unsupported relations for schema Company
[1.771ms] [rows:1] SELECT * FROM `company` WHERE id = 5 ORDER BY `company`.`id` LIMIT 1
注意:
从外键这个角度出发,我们可以发现其实只要别碰关联的外键,其实两个表就是分离的
但是总是会有抽象,比如说我
当已经存在一个
这个公司的时候
如果你还是这么写
你会发现
(这是公司的图片)
(这是员工的图片)
GORM为我们重新创建了一个公司,是不是很神奇抽象,但是其实我们从逻辑层面(属于)来讲的话,公司怎么可能随随便便就改名字呢,我们不可能在创建员工的同时把公司给改了,要不然更加抽象
下面不如总结一下创建的时候的几种情况(最主要是通过外键关系来添加,其他字段都是不重要的)
func try() error { u := User{ Name: "xxxx", Company: Company{ Name: "xxxx", }, } return _db.Create(&u).Error }
我觉得这个才是最常见的写法,因为当我们加入某个公司的时候,肯定只要把公司的名号给个人就行了,如果你想要查公司的信息,就使用preload,外键专属1
2
3
4
5
6
7
8
9
10
11
这种情况下,如果xxxx公司不存在的时候,Gorm会自动创建一个xxxx公司给这个员工,同时员工包含着这个公司的某些信息,如果公司已经成立了,他会重新创建一个同名的公司,而不是将员工属于原公司
* ````go
func try() error {
u := User{
Name: "xxxx",
CompanyID: 6,
}
return _db.Create(&u).Error
}func try() error { u := User{ Name: "xxxx", CompanyID: 6, Company: Company{ Name: "xxxx", }, } return _db.Create(&u).Error }
(IDCard) 有没有发现什么,我现在是创建IDCard,这也是能够创建的其实 **但是有一个问题!!!!!** 现在一共有两条记录  但我使用这个函数进行查找的时候1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
这种情况就很抽象了,上面讲了,很无语。
### 总结
* **属于**
* **外键**
真正有难度的只有涉及到外键的情况下,结合属于这个逻辑情况,去考虑
# Has Many
----
* ### **拥有**
我愿称之为 **纯粹的一对一**
> 例如,您的应用包含 user 和 credit card 模型,且每个 user 只能有一张 credit card。
````go
type Student struct {
ID int
Name string
IDCard IDCard // 记得不要写成 IDCard,不然他会自引用
}
type IDCard struct {
ID int
StudentID int
Number string
}
````

Gorm中的信用卡的例子我觉得不太好,因为个人觉得一个人也可以有很多信用卡,很多信用卡属于一个人说的过去,因此不如写成某个学生的学生证
按照外键的惯例,是学生证结构体指向学生结构体,按照拥有这个逻辑,学生拥有学生证
其实我突然发现,所谓的has one其实也是从某种层面来讲的
**CRUD**
[实体关联 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.](https://gorm.io/zh_CN/docs/associations.html#Association-Mode)
* **创建**
下面我有好几个尝试的案例可以先看看(原本的表中没有数据)
* **第一个**
最简单的创建,跟belongs to 一样
````go
func try() error {
return _db.Model(&Student{}).Create(&Student{
Name: "jinzhu",
IDCard: IDCard{
Number: "411422198912111122",
},
}).Error
}
````
(Student)
(IDCard)
* **第二个**
```go
func try() error {
return _db.Model(&IDCard{}).Create(&IDCard{
Number: "123456",
StudentID: 1,
}).Error
}输出为1
2
3
4
5
6func try() error {
stu := make([]Student, 0)
err := _db.Preload("IDCard").First(&stu).Error
fmt.Println(stu)
return err
}但我删除第二条记录的时候 输出为1
2
3
4=== RUN TestGormInit
[{1 jinzhu {2 1 123456}}]
--- PASS: TestGormInit (0.04s)
PASS说明虽然我能够生成很多学生证,但是查找的时候永远是按照最新一张学生证来查找的,哪怕你使用find()都没有用 从这个角度来看还是一对一的 * **第三个** 虽然不存在ID=2的学生,但是我直接创建ID=2的学生的学生证1
2
3
4=== RUN TestGormInit
[{1 jinzhu {1 1 411422198912111122}}]
--- PASS: TestGormInit (0.05s)
PASS从拥有的逻辑来看,结果毋庸置疑1
2
3
4
5
6func try() error {
return _db.Create(&IDCard{
StudentID: 2,
Number: "123456",
}).Error
}### 总结 写到这里其实我已经凌乱了,怎么会这么多情况(还只是我个人想出来的情况),那我以后怎么写,有必要总结一下 1. 正常情况通过Stu来生成IDCard 2. 正常情况下一对一查询IDCard 3. 不要乱七八糟去生成IDCard,太丑陋了1
2Error 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`pick`.`id_card`, CONSTRAINT `fk_student_id_card` FOREIGN KEY (`student_id`) REFERENCES `student` (`id`))
[9.348ms] [rows:0] INSERT INTO `id_card` (`student_id`,`number`) VALUES (2,'123456')更新
同上,个人觉得正常人应该不会去修改关联,因此平常更新也就是平常的数据库操作更新,也就是与关联无关的字段的更新(上面的链接中也有介绍怎么修改关联)
删除
同Belongs To
查询
同Belongs To尼玛个der
查询的时候要按**”拥有”**这个逻辑来查询,学生拥有学生证,因此应该是通过学生去查学生证
其实本来我还想看看Belongs To中,能不能一个员工有多个公司,但是后来我发现我好抽象
正确
1
2
3
4
5
6
7func try() error {
var stu Student
err := _db.Preload("IDCard").First(&stu, 1).Error
fmt.Println(stu)
return err
//{1 jinzhu {1 1 411422198912111122}}
}错误
1
2
3
4
5
6
7
8
9func try() error{
var card IDCard
err := _db.Preload("Student").First(&card, 1).Error
fmt.Println(card)
return err
// 2024/05/08 21:30:20 H:/today/DB/mysql.go:78 Student: unsupported relations for schema IDCard
// [3.565ms] [rows:1] SELECT * FROM `id_card` WHERE `id_card`.`id` = 1 ORDER BY `id_card`.`id` LIMIT 1
// {1 1 411422198912111122}
}
总结#
其实 Belongs To 与 Has Many到底有什么区别,这是我之前一直关注的
但是现在我发现,真正的CRUD都是相同的(使用关联的知识)
因此我们更多的时候应该通过逻辑意义上去CRUD,而不是像我上面这样抽象,在符合逻辑下的处理后,再使用关联中的知识进行CRUD还是没啥问题的,没啥大病反正
根据官方文档去使用外键!!!!
最后贴一个老板的写法,去除外键
1 | type Teacher struct { |
Has Many#
- 一对多
直接上案例吧,这个挺简单的,跟has one有点像
基本写法:
1 | type Dog struct { |
增
和 has one 一样
1 | d := Dog{ |
查询
普通查找
1
2var girl GirlGod
_db.Preload("Dogs").First(&girl) // 记住是Dogs,不是Dog,是这个字段查找数组中的指定元素(简单)
1
2var girl GirlGod
_db.Preload("Dogs", "name = ?", "汪汪1号").First(&girl) // 记住是Dogs,不是Dog,是这个字段查找数组中的指定元素(复杂形式)
1
2
3
4
5var girl GirlGod
_db.Preload("Dogs", func(db *gorm.DB) *gorm.DB {
return db.Where("name = ?", "汪汪1号")
}).First(&girl)
fmt.Println(girl)
链式查询#
现在复杂一下结构体
一个 has one 一个 has many
1 | type Info struct { |
然后按照刚才的老套路查询一下
1 | func try() error { |
可以发现,狗的信息并没有被查出来,都是0,0
因此我们需要修改一下:dagger:多加一个preload,注意一定是Dogs.Info
- 普通查询
查询某个人的狗的信息
1 | func try() error { |
- 进阶查询
现在我要查询某条狗的信息
名字叫汪汪1号的狗的信息
1 | func try() error { |
- 再进阶查询
现在我要查询某条狗的某个特殊信息
钱比100元多的且名字叫汪汪1号的狗的信息
1 | func try() error { |
总结
查询条件只适用于 当前存在的那一层
Join 提前连表#
我觉得看下面一个代码就够了
表示girl要带出一个钱大于200的狗
钱跟狗是一对一关系,并且限定条件在Info上,因此使用Joins
1 | func try() error { |
一对一
Join只能适用于一对一的关系
为什么不用Preload
如果我适用Preload,我的当前结构体是Dogs,因此当前字段是没有money字段的,
而我适用Joins的时候,属于提前连表,因此Info结构体的字段我也能够使用了
总结
感觉Joins更多的用于比如说我要获得A结构体的值,但是限定条件是B的条件,但是B的条件无法获得,所以需要使用Joins
Many 2 Many#
前面都会了,这个更简单
记得写外键,不然他不清楚中间表
1 | type Info struct { |
不想写具体操作,拜拜