GORM想要传达的一些概念
学习GORM之前需要理解ORM中的一些概念, 理解这些概念对于流畅使用GORM函数会有非常大的帮助, 尝试带着SQL的理念在这里使用, 最后很容易混淆, 对于GORM的使用也止步于简单操作, RAW SQL
- 怎么用ORM模型设置关联关系 跳转
- 外键是怎么表示的, 如何设置默认外键 跳转
- 如何取消主键限制, 如何使用自定义外键 跳转
- 什么是Association, 如何管理你的关联关系 跳转
- 利用Many2Many创建关联表 跳转
- 利用Preload做联表 跳转
观念上的变化
场景: 假设存在卡片card-1/2/3, 卡片之间存在某种关联关系
- 在Go程序中分别创建结构体 Cards 以及 CardAssociation
- 在SQL中创建表: cards + card_associations
- 当我们需要创建卡片的时候执行
create cards..
, 当我们需要创建关联的时候我们执行create associations..
→ 因此在传统SQL观念里, 我们单独设置出两张表, 然后有需要来了, 我们会单独对这两张表操作. 下面看看在GORM里是怎么做的
type Card struct {
gorm.Model
Attribute1 string
Attribute2 string
AssociatedCards []Card
}
复制代码
→ 与传统SQL最大的不同出现了,在Card结构体里, 我们嵌套了一些其他Card结构体, 传统SQL中"相互关联"的概念, 在ORM中成为了"相互拥有"的概念.
- 传统SQL: card-1 ~ card-2 关联关系
- GORM: card-1 > card-2 拥有关系
外键设定
一旦能接受新的模式, 就可以说一说外键设定了. 两个结构体之间相互关联, 最直接的想法是, 我怎么从一个结构体 出发然后去获得另一个结构体,
我怎么去声明一条外键
如果我需要通过User去查找它拥有哪些CreditCard, 那么实际上我做的事情 = "拿着User主键去CreditCard表查询". 这个User的主键, 在CreditCard表里叫什么名字? 这个名字就是我们即将设置的外键. 请牢记这个概念
不设置外键, 使用默认外键
type CreditCard struct {
gorm.Model
Number string
UserID uint
}
type User struct {
gorm.Model
CreditCards []CreditCard
}
复制代码
在GORM里我们只要创建这两个表, 关联关系就生成了, User就会包含很多个CreditCard对象.假设现在你希望通过User去查询它 关联了那些卡片, 你可以这么做:
target := []CreditCard{}
source := &User{
Model:gorm.Model{
ID:1,
},
}
database.Model(source).Related(&target)
复制代码
继续外键设置的问题, 如果我们不去指定外键名称, 那么我们拿着User主键去CreditCard查询的时候, 默认使用的属性名:user_id
, 执行:
select * from credit_cards where user_id = 123;
复制代码
请注意一个坑, 默认外键是: 表名+id,
- User表, 默认外键名user_id
- abc表, 默认外键名称abc_id
手动指定外键
type User struct {
gorm.Model
MemberNumber string
CreditCards []CreditCard `gorm:"foreignkey:UserMemberNumber;association_foreignkey:MemberNumber"`
}
type CreditCard struct {
gorm.Model
Number string
UserMemberNumber string
}
复制代码
foreignkey
在此之前,CreditCard表里必须要有User_ID, 作为外键, 但是指定foreign_key之后, 就不是一定要有User_id字段了association_foreignkey
, 在之前我们都是规定, 必须使用 UserID(主键) 去CreditCard表里查找, 但是使用了这个Tag以后你就可以通过MemberNumber (非主键) 去查找了
联表 - Association
一旦理解外键是怎么设置的, 我们就可以开始用上外键了, 概念:Association
是一个笼统的工具, 用于管理所有关联关系,以上面的Card&User为例, 我们来管理以上 两表之间的关联关系(上面两表,采用默认外键/主键的方式相互关联)
cs := []CreditCard{}
xiaohan := &User{
Model:gorm.Model{
ID:1,
},
}
// 所有与xiaohan(id=1)相关联的CreditCard,找出来
d.Model(xiaohan).Association("CreditCards").Find(&cs)
// xiaohan数据取消与ID=1的CreditCard取消关联
d.Model(xiaohan).Association("CreditCards").Delete(&CreditCard{
Model:gorm.Model{
ID:1,
},
})
// xiaohan与CreditCard之间添加关联
d.Model(xiaohan).Association("CreditCards").Append(&cards)
// 取消所有关联
db.Model(xiaohan).Association("Languages").Clear()
// 对象关联计数
db.Model(xiaohan).Association("Languages").Count()
复制代码
关联创建的时候存在一个坑,这个坑不注意可能会导致你的数据被抹掉 意识到,User→CreditCard时, 一个User面对的是多个CreditCard →因此在Append里面填充的是数组,而不是一个单个的对象.
- 如果一对一的关联关系, 填充单个对象
- 如果是一对多的关系, 一定要填充对象组(&[]Array). 否则会报错找不到, 默认策略中找不到会创建新的, 来替代, 从而数据被洗掉
- 取消找不到创建新的替代, 策略
- 先尝试Find再去Append关联关系
学会利用many2many管理关联关系
card_id | association_id |
---|---|
card-1 | card-2 |
card-1 | card-3 |
在你创建表以后, AssociationCards
虽然也是一条属性, 但是并不会像别的字段一样出现在表里, 因为在这里我们讨论的是关联 这个字段被忽略了, 不会出现在表中.取而代之的是出现了一张表: 名字就叫做card_associations
正如你想表示的那样, 一个Card对象关联着许多别的Card对象, 在这张关联表card_associations
中, 一方面是你的card_id
另一方面也是你设置的association_id
.
简单说, 就是你不用手动去设置了,经过这样描述以后, 表会为你创建
学会利用这种关系
// 创建一张卡片, 不做任何关联关系, ID自增
database.Create(&Card{})
// 创建一张卡片, 同时关联一张卡片
// 这里如果卡片存在, 直接关联, 如果不存在则会为你关联
database.Create(&Card{
AssociatedCards: []Card{
{
Model:gorm.Model{ID:2},
},
},
})
// 只关联两张卡片,将4&7两张卡片关联起来
database.Model(&Card{Model:gorm.Model{ID:7}}).
Association("AssociatedCards").
Append(&Card{Model:gorm.Model{ID:4}})
复制代码
联表 - Preload
事实上,理解了Association这种思想以后,再去理解Preload就会容易一些, 在GORM里关联/外键这样的概念被转换成结构体之间的相互包含. 继续上面的例子,聊聊Card之间的"自关联", 怎么去查询id=3
卡片所关联的卡片?
type Card struct {
ID -> 3
AssociatedCards -> ?
}
// 我们这样做:
item := &Card{
Model:gorm.Model{
ID:3,
},
}
database.Preload("AssociatedCards").Find(item)
fmt.Printf("Here the item is %v \n",item)
复制代码
一级操作:Find 这行代码的执行过程是这样的,上来先执行Find(&item)
, 也就是我们要先查询出ID=3的对象出来, 既然查出来了, 接下来就可以查看它关联了那些卡片了.
二级操作:最外Preload 我们做Preload, AssociatedCards
是一个属性, 同时在这里也象征了,所有关联的卡片, 我们只要取出这一行, 就能拿出所有关联的属性
三级操作:次外Preload ...
如果理解以上Preload的执行原理, 以及执行顺序以后,我们就可以开始在上面玩一些花样了,我们查出来的是一些Card对象, 玩的原理是对各级操作做限制, 诸如 order , where , not in 之类的操作
// 对一级操作Find上限制,这里的where是作用于Find的
database.Where(xxx)
Preload("AssociatedCards").
Find(item)
// 对二级操作上限制 - 排序
database.Preload("AssociatedCards", func(db *gorm.DB) *gorm.DB {
return db.Order("cards.id DESC")
}).Find(item)
// 对二级操作上限制 - 操作
database.Preload("AssociatedCards", "id not in (?)","1,2").Find(item)
复制代码