2 一对多表
https://gorm.io/zh_CN/docs/has_many.html
GORM 定义一个 gorm.Model
结构体,其包括字段 ID
、CreatedAt
、UpdatedAt
、DeletedAt
// gorm.Model 的定义,因为在 gorm.Model 中的这几个字段都是数据库中常用字段
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
您可以将它嵌入到您的结构体中,以包含这几个字段,详情请参考 嵌入结构体
2.1 一对多入门
- has many 关联就是创建和另一个模型的一对多关系
- 例如, 例如每一个用户都拥有多张信用卡,这样就是生活中一个简单的一对多关系
如上图:
- 比如说我将他们拆分成两张表,分别是 user 表和 card 表
- 然后再 card 表中可以看到这几张卡 ID 都是关联了 User 表中 UserID 为 1 的数据,而且可以看到 ID 只是一个 int ,所以在内存中占用的字节数就特别小
所谓的一对多其实就是一张表里面的数据可以对应其他表里面的多个数据
下面伪代码中就是如何将一对多进行定义
// 用户有多张信用卡,userID 是外键
type User struct {
gorm.Model
CreditCrards []CreditCard
}
type CreditCard struct {
gorm.Model
Number string
UserId uint // 默认会在 CreditCard 表中生成 UserID 字段作为与 User 表关联的外键 ID
}
/*
这里为啥 User 和 CreditCard 两个表之间能够实现外键关联:
因为可以看到在 User 中有一个 CreditCrards 元素,并且他的数据类型就是 []CreditCrards,并且就会自动实现关联
*/
2.1.1 外键
- 为了定义一对多关系, 外键是必须存在的,默认外键的名字是所拥有者类型的名字加上它的主键 (如上 UserID ) 。
- 就像上面的例子,为了定义一个属于 User 的模型,外键就应该为 UserID 。
- 使用其他的字段名作为外键, 你可以通过
foreignkey
字段来定制它,也就是说当我们不想使用UserID
这种默认的方式来实现外键关联,就可以通过foreignkey
字段来自定义其他字段用于作为关联外键, 例如:
type User struct {
gorm.Model
// foreignkey:UserRefer 可以自己指定外键关联字段名为:UserRefer
// 默认外键字段名是 UserId,你也可以自己修改
CreditCrards []CreditCard `gorm:"foreignkey:UserRefer"` // 这里将外键修改为了 UserRefer
}
type CreditCard struct {
gorm.Model
Number string
UserRefer uint
}
2.2 一对多表操作演示
2.2.1 表结构定义
外键约束:
- 所谓外键约束就是当我们关联的那种表中的主键被删除了,那么关联他的表里面的数据该如何操作,如 User 这张中的
zhangsan
字段表被删除了那么 CreditCard 表中关联zhangsan
外键的数据该作何操作 - 你可以通过为标签
constraint
配置OnUpdate
、OnDelete
实现外键约束,在使用 GORM 进行迁移时它会被创建
创建一对多表
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 2.定义一对多表结构
// 2.1 定义一个 user 表结构
type User struct {
gorm.Model // 基于该结构体实现数据库常用字段的创建
Username string `json:"username" gorm:"cplumn:username"`
// 添加外键关联表
CreditCards []CreditCard
}
// 2.2 定义一张 Card 表结构
type CreditCard struct {
gorm.Model
Number string
// 如果在 User 中添加了外键关联之后一定要在关联的表中加一个 UserID 并实现自动关联
// foreignkey:UserRefer 可以自己指定外键关联字段名为:UserRefer
// 默认外键字段名是 UserId,你也可以自己修改
UserID uint // 这是 gorm 中的定义方式,这个就是与 User 表关联的外键,名字 结构体+主键 ,这里 user 是结构体 ID 是主键
}
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{}, &CreditCard{})
}
# 可以看到现在已经创建了两张表了
mysql> show tables;
+-------------------+
| Tables_in_test_db |
+-------------------+
| credit_cards |
| users |
+-------------------+
3 rows in set (0.00 sec)
现在我们创建完了表之后,在 user 表中添加数据是否能够实现自动关联到 CreditCard 表中呢?请看下面例子
2.2.2 创建一对多数据
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 2.定义一对多表结构
// 2.1 定义一个 user 表结构
type User struct {
gorm.Model // 基于该结构体实现数据库常用字段的创建
Username string `json:"username" gorm:"cplumn:username"`
// 添加外键关联表
CreditCards []CreditCard
}
// 2.2 定义一张 Card 表结构
type CreditCard struct {
gorm.Model
Number string
// 如果在 User 中添加了外键关联之后一定要在关联的表中加一个 UserID 并实现自动关联
UserID uint // 这是 gorm 中的定义方式,这个就是与 User 表关联的外键,名字 结构体+主键 ,这里 user 是结构体 ID 是主键
}
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{}, &CreditCard{})
// 4.创建一对多数据
// 创建 zhangsan 用户并关联两个数据,由于 CreditCards 是 []CreditCard 类型所以这里在 CreditCards 表中创建了两个卡号
user := User{
Username: "zhangsan",
CreditCards: []CreditCard{
{Number: "卡号:0001"},
{Number: "卡号:0002"},
},
}
db.Create(&user)
}
可以看到 user
表中 ID : 1
的数据分别关联了 CreditCards
表中的 id = 1、2
的数据
而且通过上面的代码我们还可以看到在 CreditCards
表中并没有写 id
字段也没有将其设置为主键,但是他会自动的将一对多额外键关联实现添加
2.2.3 添加数据
当这个时候我们知道了在 users 表中有 zhangsan 这个用户,那么我们想给 zhangsan 这个用户在添加一条数据就看下面示例
// 在下面代码中我们将给 zhangsan 这个用户添加一条 creditcarcd 数据
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 2.定义一对多表结构
// 2.1 定义一个 user 表结构
type User struct {
gorm.Model // 基于该结构体实现数据库常用字段的创建
Username string `json:"username" gorm:"cplumn:username"`
// 添加外键关联表
CreditCards []CreditCard
}
// 2.2 定义一张 Card 表结构
type CreditCard struct {
gorm.Model
Number string
// 如果在 User 中添加了外键关联之后一定要在关联的表中加一个 UserID 并实现自动关联
UserID uint // 这是 gorm 中的定义方式,这个就是与 User 表关联的外键,名字 结构体+主键 ,这里 user 是结构体 ID 是主键
}
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{}, &CreditCard{})
// 4.创建一对多数据
// 创建 zhangsan 用户并关联两个数据,由于 CreditCards 是 []CreditCard 类型所以这里在 CreditCards 表中创建了两个卡号
//user := User{
// Username: "zhangsan",
// CreditCards: []CreditCard{
// {Number: "卡号:0001"},
// {Number: "卡号:0002"},
// },
//}
//db.Create(&user)
// 5.给zhangsan 添加一张信用卡
user := User{Username: "zhangsan"}
// 先找到 zhangsan 用户,在进行操作
db.First(&user)
// 通过 Association 方法将外键表 CreditCards 进行关联,然后再使用 append 方法添加一个 []CreditCard 中的某个字段即可
db.Model(&user).Association("CreditCards").Append([]CreditCard{{Number: "卡号:0003"}})
}
可以看到已经将 0003 添加成功
![image-20220725225636605](GORM 入门.assets/image-20220725225636605.png)
2.2.4 一对多关联查询
2.2.4.1 Association 方法查询
- 使用
Association
方法前, 需要把 User 查询到 - 然后根据 User 定义中指定的
AssociationForeignKey
去查找CreditCard
所谓的关联查询,就是如何在主表中查询到关联表的信息,也就是说在这个示例中如何通过 user 表来查询到 zhangsan 数据所关联的 CreditCard 表信息
package main
import (
"encoding/json"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 2.创建表结构
type User struct {
gorm.Model
Username string `json:"username" gorm:"cplumn:username"`
CreditCards []CreditCard
}
type CreditCard struct {
gorm.Model
Number string
UserID uint
}
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.查询关联表数据
// 3.1首先需要先查询到 zhangsan 这个用户
user := User{Username: "zhangsan"}
db.First(&user)
// 3.2通过查询到 zhangsan 数据的 AssociationForeignKey 来继续关联查询 CreditCards 表
/* 通过查询到 zhangsan 之后的这个 user 结构体,并且基于该结构体中的 CreditCards 字段进行查询,因为该字段是关联表信息。
最后在通过 find 查询,&user.CreditCards = zhangsan.CreditCards 也就是间接查询 zhangsan 用户的 CreditCards 信息
*/
db.Model(&user).Association("CreditCards").Find(&user.CreditCards)
for _, v := range user.CreditCards {
fmt.Printf("%s:%v\n", user.Username, v)
}
// json 格式输出
userJson, _ := json.Marshal(user.CreditCards)
fmt.Println(string(userJson))
}
$ go run main.go
zhangsan:{{1 2022-07-25 17:51:22.782 +0800 CST 2022-07-25 17:51:22.782 +0800 CST {0001-01-01 00:00:00 +0000 UTC false}} 卡号:0001 1}
zhangsan:{{2 2022-07-25 17:51:22.782 +0800 CST 2022-07-25 17:51:22.782 +0800 CST {0001-01-01 00:00:00 +0000 UTC false}} 卡号:0002 1}
zhangsan:{{3 2022-07-25 22:55:11.049 +0800 CST 2022-07-25 22:55:11.049 +0800 CST {0001-01-01 00:00:00 +0000 UTC false}} 卡号:0003 1}
# 执行之后可以看到已经将 zhangsan 的关联表 CreditCards
通过 json 可以看到对应的三条 CreditCard 关联表信息
![image-20220725233401051](GORM 入门.assets/image-20220725233401051.png)
2.2.4.2 preload 预加载查询(使用较多)
- 所谓预加载就是通过单独一张表来查询出对应的关联关系,而不是通过
AssociationForeignKey
去查找,这样就避免了查询两次的问题。 - 如果想通过一次就查询出对应关联关系那就可以使用预加载的方式
使用 Preload
方法, 在查询 User 时先去获取 CreditCard 的记录
package main
import (
"encoding/json"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 2.创建表结构
type User struct {
gorm.Model
Username string `json:"username" gorm:"cplumn:username"`
CreditCards []CreditCard
}
type CreditCard struct {
gorm.Model
Number string
UserID uint
}
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.预加载查询,通过 preload 方法实现
// 3.1 加载所有 user 数据信息
u := []User{} // 先创建一个 []User
db.Preload("CreditCards").Find(&u) // 通过对关联表进行查询,这里在 user 结构体中关联表字段就是 CreditCards
// 3.2 加载指定数据信息
u := []User{Username: "zhangsan"}
db.Preload("CreditCards").Find(&u)
// 3.3 带条件的预加载
var users []User
// 查询在 user 表中关联了所有卡号为 001 的用户
db.Where("username = ?","zhangsan").Preload("CreditCards","Number = ?","卡号001").Find(&users)
// 转成 json 格式
jsonU, _ := json.Marshal(&u)
fmt.Println(string(jsonU))
}
root@ubuntu:~/go/src/devops/gorm/1ToDuo/demo3_preload# go run main.go
[{"ID":1,"CreatedAt":"2022-07-25T17:51:22.78+08:00","UpdatedAt":"2022-07-25T22:55:11.047+08:00","DeletedAt":null,"username":"zhangsan","CreditCards":[{"ID":1,"CreatedAt":"2022-07-25T17:51:22.782+08:00","UpdatedAt":"2022-07-25T17:51:22.782+08:00","DeletedAt":null,"Number":"卡号:0001","UserID":1},{"ID":2,"CreatedAt":"2022-07-25T17:51:22.782+08:00","UpdatedAt":"2022-07-25T17:51:22.782+08:00","DeletedAnull,"Number":"卡号:0002","UserID":1},{"ID":3,"CreatedAt":"2022-07-25T22:55:11.049+08:00","UpdatedAt":"2022-07-25T22:55:11.049+08:00","DeletedAt":null,"Number":"卡号:0003","UserID":1}]}]
preload 预加载和 Association 的区别:
Association 需要查询两次:
- 在这的示例中首先需要通过 First 将 user 表对应查询的数据查询出来
- 在通过 Association 方法查询对应的关联表
preload 预加载:
- 直接通过 preload 方法将对应的关联表进行查询