3 多对多表
所谓的多对多就是在我们的多张表中会有一些关联关系。双向的一对多就直接可以理解为多对多,双向一对多需要第三张表来进行表示
- Many to Many 会在两个 model 中添加一张连接表。
- 例如,您的应用包含了 user 和 language,且一个 user 可以说多种 language,多个 user 也可以说一种 language。
- 当使用 GORM 的 AutoMigrate 为 User 创建表时,GORM 会自动创建连接表
有一张 student 表,一个学生可以选择学习多个课程,然后一个课程也可以被多个学生所学习。上图中可以看到 zhangsan、lisi 同学不仅学习了 go 还学习了 vue ,而这样的情况下就会出现还有多个用户和多个课程。
并导致课程和用户就比较冗余,所以这个时候就需要将这样的一张冗余的表进行拆分。此时就需要使用到多对多的拆分,所以将拆分为了 student 和 Lesson 两张表
那么现在如何去表示基本的关联关系呢?此时就需要第三张表,所谓的第三张表就是包含他们关联关系的表,也就是在第三张表中直接将记录 Student 表和 Lesson 表之间的关联关系。
在这张表中记录 userid = 1 并且学了了 lessonID = 1 的课程、依此类推,这样的话第三张表中就没有数据冗余。
因为在第三张表中直接分别关联了 Student 表和 Lesson 表的唯一主键
3.1 多对多入门
Many to Many 会在两个 model 中添加一张连接表。
例如,您的应用包含了 user 和 language,且一个 user 可以说多种 language,多个 user 也可以说一种 language。
演示伪代码:
// User 拥有并属于多种 language,`user_languages` 是连接表
type User struct {
gorm.Model
Languages []Language `gorm:"many2many:user_languages;"` // 对多对使用 many2many 字段,user_languages 指向的是第三张表的名字
}
type Language struct {
gorm.Model
Name string
}
当使用 GORM 的 AutoMigrate
为 User
创建表时,GORM 会自动创建连接表
3.2 多对多操作演示
3.2.1 表结构创建
这里我直接创建 user 、 language 两张表,然后在自动创建第三张关联表 user_languages
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 2.定义多对多表结构
// 定义 user 表
type User struct {
gorm.Model
Name string
/*
gorm:"many2mant :在 gorm 中定义多对多的字段,表示 user 表和 Language 是多对多关联关系,一个用户可会多种语言,一个语言可被多用户使用
user_languages :指定第三张表生成名称,存放的是多对多的关系
*/
Languages []Language `gorm:"many2many:user_languages"`
}
// 定义 language 表
type Language struct {
gorm.Model
LanguageName string
}
func main() {
// 1.建立连接
dsn := "root:123456@tcp(10.0.0.134:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
// 3.创建表结构
db.AutoMigrate(&User{}, &Language{})
}
创建完成
mysql> show tables;
+-------------------+
| Tables_in_test_db |
+-------------------+
| languages |
| user_languages |
| users |
+-------------------+
4 rows in set (0.00 sec)
# 分别是三张表的关联关系
mysql> desc languages;
+---------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| created_at | datetime(3) | YES | | NULL | |
| updated_at | datetime(3) | YES | | NULL | |
| deleted_at | datetime(3) | YES | MUL | NULL | |
| language_name | longtext | YES | | NULL | |
+---------------+---------------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)
mysql> desc users;
+------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| created_at | datetime(3) | YES | | NULL | |
| updated_at | datetime(3) | YES | | NULL | |
| deleted_at | datetime(3) | YES | MUL | NULL | |
+------------+---------------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
# 在 user_languages 表中可以看到分别为 user_id、language_id,这张表只存放用户和语言的绑定关系
mysql> desc user_languages;
+-------------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------------+------+-----+---------+-------+
| user_id | bigint(20) unsigned | NO | PRI | NULL | |
| language_id | bigint(20) unsigned | NO | PRI | NULL | |
+-------------+---------------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
这种创建方式就是直接通过 gorm 来维护这张表结构,那么如果说在有些情况下不允许建这种外键关联关系的时候就需要我们自定义第三张表。
并且 gorm 也是支持这么操作的
3.2.2 自定义创建
在这个示例中是基于收货地址这种关系,一个用户可以有多个收货地址、那么这里我演示订单和用户之间的关联关系需要怎么实现呢
package main
import (
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
/*
模拟美团外卖地址填写
一个用户可以填写多个地址
一个地址可以被多个用户填写
*/
// 2.自定义多对多表结构
type Person struct {
Id int
Name string `gorm:"many2many:person_addresses"` // 实现对第三张表的关联关系
}
type Address struct {
Id uint
Name string
}
// 自定义第三张表,用来表示多对多之间的关联关系
type PersonAddress struct {
// 下面两个元素分别是 Person、Address 结构体的主键
PersonId int `gorm:"primaryKey"`
AddressId int `gorm:"primaryKey"`
CreateAt time.Time
}
func main() {
// 1.建立连接
dsn := "root:123456@tcp(10.0.0.134:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
db.AutoMigrate(&Person{}, &Address{}, &PersonAddress{})
}
# 创建成功
mysql> show tables;
+-------------------+
| Tables_in_test_db |
+-------------------+
| addresses |
| people |
| person_addresses |
+-------------------+
7 rows in set (0.00 sec)
# 以实现对 person 和 address 表的关联
mysql> desc person_addresses;
+------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| person_id | bigint(20) | NO | PRI | NULL | |
| address_id | bigint(20) | NO | PRI | NULL | |
| create_at | datetime(3) | YES | | NULL | |
+------------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
这种方式就适用于公司不允许通过其他的框架的方式来创建,就可以通过自定义创建的方式来实现
3.2.3 创建数据
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 2.定义多对多表结构
// 定义 user 表
type User struct {
gorm.Model
/*
gorm:"many2mant :在 gorm 中定义多对多的字段,表示 user 表和 Language 是多对多关联关系,一个用户可会多种语言,一个语言可被多用户使用
user_languages :指定第三张表生成名称,存放的是多对多的关系
*/
UserName string
Languages []Language `gorm:"many2many:user_languages"`
}
// 定义 language 表
type Language struct {
gorm.Model
LanguageName string
}
func main() {
// 1.建立连接
dsn := "root:123456@tcp(10.0.0.134:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
// 3.创建表结构
db.AutoMigrate(&User{}, &Language{})
// 4.创建数据
// 通过 []User 方式写入多个用户信息
u := []User{
{
UserName: "zhangsans",
Languages: []Language{
{LanguageName: "英文"},
{LanguageName: "中文"},
},
},
{
UserName: "lisi",
Languages: []Language{
{LanguageName: "日文"},
{LanguageName: "法文"},
},
},
}
db.Create(&u)
}
mysql> select * from users;
+----+-------------------------+-------------------------+------------+-----------+
| id | created_at | updated_at | deleted_at | user_name |
+----+-------------------------+-------------------------+------------+-----------+
| 1 | 2022-07-26 15:51:04.875 | 2022-07-26 15:51:04.875 | NULL | zhangsans |
| 2 | 2022-07-26 15:51:04.875 | 2022-07-26 15:51:04.875 | NULL | lisi |
+----+-------------------------+-------------------------+------------+-----------+
2 rows in set (0.00 sec)
mysql> select * from languages;
+----+-------------------------+-------------------------+------------+---------------+
| id | created_at | updated_at | deleted_at | language_name |
+----+-------------------------+-------------------------+------------+---------------+
| 1 | 2022-07-26 15:51:04.876 | 2022-07-26 15:51:04.876 | NULL | 英文 |
| 2 | 2022-07-26 15:51:04.876 | 2022-07-26 15:51:04.876 | NULL | 中文 |
| 3 | 2022-07-26 15:51:04.876 | 2022-07-26 15:51:04.876 | NULL | 日文 |
| 4 | 2022-07-26 15:51:04.876 | 2022-07-26 15:51:04.876 | NULL | 法文 |
+----+-------------------------+-------------------------+------------+---------------+
4 rows in set (0.00 sec)
# 第三张表分别关联 user_id、language_id 成功
mysql> select * from user_languages;
+---------+-------------+
| user_id | language_id |
+---------+-------------+
| 1 | 1 |
| 1 | 2 |
| 2 | 3 |
| 2 | 4 |
+---------+-------------+
4 rows in set (0.00 sec)
3.2.4 多对多 Preload
package main
import (
"encoding/json"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 2.定义多对多表结构
// 定义 user 表
type User struct {
gorm.Model
/*
gorm:"many2mant :在 gorm 中定义多对多的字段,表示 user 表和 Language 是多对多关联关系,一个用户可会多种语言,一个语言可被多用户使用
user_languages :指定第三张表生成名称,存放的是多对多的关系
*/
UserName string
Languages []Language `gorm:"many2many:user_languages"`
}
// 定义 language 表
type Language struct {
gorm.Model
LanguageName string
}
func main() {
// 1.建立连接
dsn := "root:123456@tcp(10.0.0.134:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
// 3.创建表结构
db.AutoMigrate(&User{}, &Language{})
// 4.创建数据
//u := []User{
// {
// UserName: "zhangsans",
// Languages: []Language{
// {LanguageName: "英文"},
// {LanguageName: "中文"},
// },
// },
// {
// UserName: "lisi",
// Languages: []Language{
// {LanguageName: "日文"},
// {LanguageName: "法文"},
// },
// },
//}
//db.Create(&u)
// 5.preload 预加载查询
u := User{UserName: "zhangsan"}
db.Preload("Languages").Find(&u)
jsonU, _ := json.Marshal(&u)
fmt.Println(string(jsonU))
}
$ go run main.go
{"ID":1,"CreatedAt":"2022-07-26T15:51:04.875+08:00","UpdatedAt":"2022-07-26T15:51:04.875+08:00","DeletedAt":null,"UserName":"zhangsans","Languages":[{"ID":1,"CreatedAt":"2022-07-26T15:51:04.876+08:00","UpdatedAt":"2022-07-26T15:51:04.876+08:00","DeletedAt":null,"LanguageName":"英文"},{"ID":2,"CreatedAt":"2022-07-26T15:51:04.876+08:00","UpdatedAt":"2022-07-26T15:51:04.876+08:00","DeletedAt":null,"LanageName":"中文"}]}