GORM 指南 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
一对多入门
外键是指在一个表中的某个字段引用了另一个表中的主键,通过外键可以建立表之间的关联关系。
has many
与另一个模型建立了一对多的连接。 不同于 has one
,拥有者可以有零或多个关联模型。
例如,您的应用包含 user 和 credit card 模型,且每个 user 可以有多张 credit card。
has many介绍
- has many 关联就是创建和另一个模型的一对多关系(数据库里面是一对多,然后struct里面也是一对多)
- 例如, 例如每一个用户都拥有多张信用卡,这样就是生活中一个简单的一对多关系
在设计表的时候不可能将所有的信息都放在一个表里面,那么表就会非常非常的宽。这样字段就会非常的多,性能就会受到影响。
在设计的时候这里其实就可以设置为两张表。一个是用户的详情表和信用卡的详情表,用户表里面加上卡的id和他做一个关联,那么这就是一对多的关系。
当一个数据库存储了很多数据的时候就需要分库分表,上面也类似,将一张表分为两张表。
Gorm 关联查询(又叫连表查询)中的 Has Many 关系是 一对多 的关联关系,通常用于描述一个 Model 拥有多个 Model。
比如:一个 用户拥有多张信用卡,下面以 Go Struct 表示表结构
creditcar的外键是userid。
type Model struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt DeletedAt `gorm:"index"`
}
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 继承gorm的基础Model,里面默认定义了ID、CreatedAt、UpdatedAt、DeletedAt 4个字段
type User struct {
gorm.Model
//默认创建三个时间字段和一个id字段,create_at update_at delete_at这样可以知道创建时间/更新时间/删除时间
CreditCards []CreditCard66
//代表在查询user数据的时候可以包含creditcard,这其实是有一个关联关系的,代表查询可以查出来,不代表有这个字段。方便做关联查询使用的
// 一对多关联属性,表示多张信用卡
}
//用户有多张信用卡,UserID是外键
type CreditCard struct {
gorm.Model
Number string
UserId int //默认会在CreditCard表中生成UserId字段作为与user表关联的外键id
//默认会在CreditCard表中生成UserID字段作为与User表关联的外键ID
}
func main() {
dsn := "root:7PXjAkY!&nlR@tcp(192.168.11.128:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn66), &gorm.Config{})
db.AutoMigrate(User{}, CreditCard{})
}
真正表名两张表有关联关系的是UserId这个字段。当你去查user的时候可以通过userid去去将creditcard捞出来。
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
gorm.Model
Name string
CreditCard []CreditCard
}
type CreditCard struct {
gorm.Model
Number string
UserID uint
}
func main() {
dsn := "root:7PXjAkY!&nlR@tcp(192.168.11.128:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
db.AutoMigrate(User{}, CreditCard{})
u := &User{
Name: "lucas",
CreditCard: []CreditCard{
{Number: "0001"},
{Number: "0002"},
},
}
db.Create(u)
}
mysql> select * from credit_cards;
+----+-------------------------+-------------------------+------------+--------+---------+
| id | created_at | updated_at | deleted_at | number | user_id |
+----+-------------------------+-------------------------+------------+--------+---------+
| 1 | 2023-06-29 16:02:41.504 | 2023-06-29 16:02:41.504 | NULL | 0001 | 1 |
| 2 | 2023-06-29 16:02:41.504 | 2023-06-29 16:02:41.504 | NULL | 0002 | 1 |
+----+-------------------------+-------------------------+------------+--------+---------+
2 rows in set (0.00 sec)
mysql> select * from users;
+----+-------------------------+-------------------------+------------+-------+
| id | created_at | updated_at | deleted_at | name |
+----+-------------------------+-------------------------+------------+-------+
| 1 | 2023-06-29 16:02:41.478 | 2023-06-29 16:02:41.478 | NULL | lucas |
+----+-------------------------+-------------------------+------------+-------+
1 row in set (0.00 sec)
二 外键
type User struct{
gorm.Model
CreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
}
type CreditCard struct{
gorm.Model
Number string
UserRefer uint
}
`gorm:"foreignKey:UserRefer"` 可以使用这个字段来去改它的外键,但是外键参考的还是user表的id。
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
gorm.Model
CreditCards []*CreditCard `gorm:"foreignKey:UserRefer"`
}
type CreditCard struct {
gorm.Model
Number int
UserRefer int
}
func main() {
dsn6 := "root:7PXjAkY!&nlR@tcp(192.168.11.128:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn6), &gorm.Config{})
db.AutoMigrate(&User{}, &CreditCard{})
}
mysql> desc users;
+------------+-----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-----------------+------+-----+---------+----------------+
| id | bigint 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 | |
+------------+-----------------+------+-----+---------+----------------+
mysql> desc credit_cards;
+------------+-----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-----------------+------+-----+---------+----------------+
| id | bigint 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 | |
| number | bigint | YES | | NULL | |
| user_refer | bigint unsigned | YES | MUL | NULL | |
+------------+-----------------+------+-----+---------+----------------+
mysql> drop tables users;
ERROR 3730 (HY000): Cannot drop table 'users' referenced by a foreign key constraint 'fk_users_credit_cards' on table 'credit_cards'.
mysql> show create table credit_cards;
CONSTRAINT `fk_users_credit_cards` FOREIGN KEY (`user_refer`) REFERENCES `users` (`id`)
一般不推荐使用这种方式,还是推荐使用Userid的方式。
三 外键关联
type User struct {
gorm.Model
MemberNumber string
// 默认CreditCard会使用User表的Id作为外键,association_foreignkey:MemberNumber
// 指定使用MemberNumber 作为外键关联
CreditCards []CreditCard
`gorm:"foreignkey:UserMemberNumber;association_foreignkey:MemberNumber"`
}
type CreditCard struct {
gorm.Model
Number string
UserMemberNumber string
}
其实就是creditcard外键UserMemberNumber直接去找user的 MemberNumber。
上面其实也就是改变了外键的默认值,默认外键是Userid,那么现在变为了MemberNumber。
一般使用默认id的外键就能够覆盖很多场景,上面这种只是举例。
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
gorm.Model
MemberNumber string
CreditCards []*CreditCard `gorm:"foreignKey:UserMemberNumber;association_foreignKey:MemberNumber"`
}
type CreditCard struct {
gorm.Model
Number int
UserMemberNumber string
}
func main() {
dsn6 := "root:7PXjAkY!&nlR@tcp(192.168.11.128:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn6), &gorm.Config{})
db.AutoMigrate(&User{}, &CreditCard{})
}
mysql> desc users;
+---------------+-----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+-----------------+------+-----+---------+----------------+
| id | bigint 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 | |
| member_number | longtext | YES | | NULL | |
+---------------+-----------------+------+-----+---------+----------------+
mysql> desc credit_cards;
+--------------------+-----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------------+-----------------+------+-----+---------+----------------+
| id | bigint 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 | |
| number | bigint | YES | | NULL | |
| user_member_number | bigint unsigned | YES | MUL | NULL | |
+--------------------+-----------------+------+-----+---------+----------------+
创建一对多表
/*
constraint:OnUpdate:CASCADE 【当User表更新,也会同步给CreditCards】 // 外键约束
OnDelete:SET NULL 【当User中数据被删除时,CreditCard关联设置为 NULL,不删除记录】
*/
type User struct {
gorm.Model
Username string `json:"username" gorm:"column:username"`
CreditCards []CreditCard `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
type CreditCard struct {
gorm.Model
Number string
UserID uint
}
func main() {
// 0、连接数据库
dsn := "root:1@tcp(127.0.0.1:3306)/test_db?
charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
// 创建表结构
db.AutoMigrate(User{}, CreditCard{})
// 1、创建一对多
user := User{
Username: "zhangsan",
CreditCards: []CreditCard{
{Number: "0001"},
{Number: "0002"},
},
}
db.Create(&user)
// 2、为已存在用户添加信用卡
u := User{Username: "zhangsan"}
db.First(&u)
//fmt.Println(u.Username)
}
一对多Association 查找关联
使用 Association 方法, 需要把把 User 查询好, 然后根据 User 定义中指定AssociationForeignKey 去查找CreditCard。
查询某条数据使用first 查询唯一值
u := &User{Id: 1}
db.Debug().First(u)
fmt.Println(u)
db.Where("id=?",u.Id).First(u)
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
gorm.Model
UserName string `json:"username" gorm:"column:username"`
CreditCards []CreditCard `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
type CreditCard struct {
gorm.Model
Number string
UserID int
}
func main() {
dsn6 := "root:7PXjAkY!&nlR@tcp(192.168.11.128:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn6), &gorm.Config{})
db.AutoMigrate(&User{}, &CreditCard{})
/*
u := &User{
Model: gorm.Model{},
UserName: "lucas",
CreditCards: []*CreditCard{
{Number: "0001", UserID: 1},
{Number: "0002", UserID: 2},
},
}
db.Create(u)
*/
//查找 用户名为 lucas 的所有信用卡信息
u1 := &User{UserName: "lucas"}
//Association必须要先查出User才能关联查询对应的CreditCard
db.First(u1)
db.Model(u1).Association("CreditCards").Find(&u1.CreditCards)
fmt.Println(u1)
}
这里只查找了creditcard
[1.513ms] [rows:2] SELECT * FROM `credit_cards` WHERE `credit_cards`.`user_id` = 1 AND `credit_cards`.`deleted_at` IS NULL
associate是先去获取user,再去获取creditcard !!!!!!!!! var u User db.Debug().Where("name = ?", "lucas").Find(&u) fmt.Println(u) [2.756ms] [rows:1] SELECT * FROM `user` WHERE name = 'lucas' {1 lucas []}
// 关联查询的结果,保存到user.CreditCard属性 db.Debug().Model(&u).Association("Articles").Find(&u.Articles) fmt.Println(u) [0.712ms] [rows:2] SELECT * FROM `article` WHERE `article`.`user_id` = 1 {1 lucas [{1 k8s 1 {0 []}} {2 golang 1 {0 []}}]}
追加 db.Model().Association("CreditCards").Append
package main
import (
"encoding/json"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
gorm.Model
UserName string `json:"username" gorm:"column:username"`
CreditCards []CreditCard `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
type CreditCard struct {
gorm.Model
Number string
UserID int
}
func main() {
dsn6 := "root:7PXjAkY!&nlR@tcp(192.168.11.128:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn6), &gorm.Config{})
db.AutoMigrate(&User{}, &CreditCard{})
u := &User{
Model: gorm.Model{},
UserName: "lucas",
CreditCards: []CreditCard{
{Number: "0001", UserID: 0},
{Number: "0002", UserID: 1},
},
}
db.Create(u)
u1 := &User{UserName: "lucas"}
db.First(u1)
db.Model(u1).Association("CreditCards").Find(&u1.CreditCards)
fmt.Println(u1)
db.Model(u1).Association("CreditCards").Append([]CreditCard{
{Number: "0008", UserID: 1},
})
fmt.Println(u1)
strUser, _ := json.Marshal(u1)
fmt.Println(string(strUser))
}
&{{1 2023-04-16 10:48:54.423 +0800 CST 2023-04-16 14:12:45.825 +0800 CST {0001-01-01 00:00:00 +0000 UTC false}} lucas []}
&{{1 2023-04-16 10:48:54.423 +0800 CST 2023-04-16 14:15:51.44 +0800 CST {0001-01-01 00:00:00 +0000 UTC false}} lucas [{{17 2023-04-16 14:15:51.445
+0800 CST 2023-04-16 14:15:51.445 +0800 CST {0001-01-01 00:00:00 +0000 UTC false}} 0008 1}]}
{"ID":1,"CreatedAt":"2023-04-16T10:48:54.423+08:00","UpdatedAt":"2023-04-16T14:15:51.44+08:00","DeletedAt":null,"username":"lucas","CreditCards":[
{"ID":17,"CreatedAt":"2023-04-16T14:15:51.445+08:00","UpdatedAt":"2023-04-16T14:15:51.445+08:00","DeletedAt":null,"Number":"0008","UserID":1}]}
一对多Preload 预加载
使用 Preload 方法, 在查询 User 时先去获取 CreditCard 的记录
和上面associate不一样,他们两个最大的区别是执行的顺序是不一样的,associate是先去获取user,再去获取creditcard。
preload是先去获取creditcard,再去获取user。
package main
import (
"encoding/json"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
/*
constraint:OnUpdate:CASCADE 【当User表更新,也会同步给CreditCards】
OnDelete:SET NULL 【当User中数据被删除时,CreditCard关联设置为 NULL,不删除记录】
*/
type User struct {
gorm.Model
Username string `json:"username" gorm:"column:username"`
CreditCards []CreditCard `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET
NULL;"`
}
type CreditCard struct {
gorm.Model
Number string
UserID uint
}
func main() {
// 0、连接数据库
dsn := "root:1@tcp(127.0.0.1:3306)/test_db?
charset=utf8mb4&parseTime=True&loc=Local"
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{})
// 1、预加载: 查找 user 时预加载相关 CreditCards
//users := User{Username: "zhangsan"} // 只查找张三用户的信用卡信息
users := []User{}
db.Preload("CreditCards").Find(&users)
查询结果
[
{
"ID":1,
"username":"zhangsan",
"CreditCards":[
{
"ID":1,
"Number":"0001",
"UserID":1
},
...
]
}
]
u2 := &User{UserName: "lucas"}
db.Preload("CreditCards").Find(u2)
fmt.Println(u2)
可以看到即使username不是外键,它现在的数据是不完整的。那么我依然可以通过username去找到user这条数据。并且可以将creditcard数据也一次性的找出来。
2023/06/12 16:47:34 C:/Users/W10/GolandProjects/day1/gorm/hasmany1.go:42
[1.726ms] [rows:2] SELECT * FROM `credit_cards` WHERE `credit_cards`.`user_id` = 1 AND `credit_cards`.`deleted_at` IS NULL
2023/06/12 16:47:34 C:/Users/W10/GolandProjects/day1/gorm/hasmany1.go:42
[2.751ms] [rows:1] SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL
预加载先查了credit card,再去查找user,它会去查两遍。
//preload和association的区别别
//preload会多查询一次,返回的是整个结构体内容,包括属性的结构体//association只查属性的结构体对应的表,返回的是属性的结构体//association支持增删改查,preload仅支持查询//一般情况下,preload用于关联查询,Gssociation用于关联的增删改查