关联的概述
首先了解一下关联的作用:自动创建/更新。
在创建、更新记录时,GORM会通过Upsert自动保存关联及其引用记录。一定要记住关联的作用,我们才好理解后续的关联关系分类。
关联模式在官方文档被分成了四种类型:Belongs To,Has One,Has Many,Many To Many。接下来会一次介绍这四种类型,同时展示关联的作用在使用时是如何体现的。
Belongs To
介绍
Belongs To会与另一个模型建立了一对一的连接。 这种模型的每一个实例都属于另一个模型的一个实例。
示例
具有Belongs To模型示例如下:
type Dog struct {
gorm.Model
Name string
BeautyId int
Beauty Beauty
}
type Beauty struct {
gorm.Model
Name string
}
如上述示例代码,某只舔狗倾心于一个美人,他的心属于这个美人。仔细看Belongs To的构造,舔狗属于美女,所以舔狗的结构体内有美女的实体信息以及ID,所以Belongs To关系通过被拥有者包含拥有者的ID/实体来展现。
要定义一个Belongs To关系,数据库的表中必须存在外键。默认情况下,外键的名字,使用拥有者的类型名称加上表的主键的字段名字(上例Beauty
是拥有者,默认外键名应声明为BeautyId
),在创建记录时,GORM会自动把主表的ID属性保存到被拥有者的外键属性中(将Beauty.ID
保存到Dog.BeautyId
中,称Beauty.Id
为引用)。
NOTE:上述的外键名/引用都是框架默认的,要自定义外键名/引用,通过标签
foreignkey/references
来指定。
// 重写外键
type Dog struct {
gorm.Model
Name string
BeautyRef int
Beauty Beauty `gorm:"foreignKey:BeautyRef"`
}
type Beauty struct {
gorm.Model
Name string
}
// 重写引用
type Dog struct {
gorm.Model
Name string
BeautyId string // 注意外键类型随着引用类型进行改变
Beauty Beauty `gorm:"references:Name"`
}
type Beauty struct {
gorm.Model
Name string
}
NOTE:如果外键名恰好在拥有者类型中存在,GORM通常会错误的认为它是has one关系。我们需要在Belongs To关系中指定references。
type Dog struct {
gorm.Model
Name string
BeautyId int
Beauty Beauty `gorm:"references:Name"`
}
type Beauty struct {
BeautyId int
Name string
}
CRUD(以Belongs To为例,其他基本同理)
建表
db, _ := gorm.Open(mysql.Open("user:password@tcp(127.0.0.1:3306)/demo?charset=utf8"))
// 创建dogs/beauties两张表
db.AutoMigrate(&Dog{})
// 创建beauties一张表
db.AutoMigrate(&Beauty{})
得益于Belong To关联,通过db.AutoMigrate(&Dog{})
函数可以直接创建dogs/beauties
两张表,由于Beauty
不需要其他类便可独自声明,所以db.AutoMigrate(&Beauty{})
只创建一张表。
创建记录
// beauty表也会产生对应的记录(关联模式)
beauty := Beauty{
Model: gorm.Model{ID: 1},
Name: "b1",
}
dog := Dog{
Model: gorm.Model{ID: 1},
Name: "d1",
Beauty: beauty,
}
db.Create(&dog)
// 仅创建b2一个记录
beauty = Beauty{
Model: gorm.Model{ID: 2},
Name: "b2",
}
db.Create(&beauty)
同样的,通过关联模式,创建dog
类型的数据也会同时创建beauty
类型的记录。
更新记录
// 接前面代码
dog.Name = "d11"
dog.Beauty.Name = "b11"
// 只会更新dog.Name
db.Updates(&dog)
// 使用FullSaveAssociations可以更新关联数据
db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&dog)
如果想要更新关联的数据,需要使用FullSaveAssociations
模式。
如何跳过自动创建/更新:使用Select/Omit
// 在创建Dog记录之前,需要先确保Beauty对象已经在beauties表中存在(除非像Select("Name")一样不牵涉外键相关属性
db.Select("Name").Create(&dog) // 仅保存Name属性
db.Omit("Beauty").Create(&dog) // 仅跳过Beauty
db.Omit(clause.Associations).Create(&dog) // 跳过所有关联(如果有)
查询记录(预加载)
当主结构包含另一个结构并且它们确立了关系(Belongs To,Has One等),GORM允许使用Preload
通过多个SQL中来直接加载关系。
db.Preload("Beauty").Find(&dog)
db.Preload(clause.Associations).Find(&dog)
// preload可以带条件
db.Preload("Beauty", "name = ?", "b1").Find(&dog)
db.Preload("Beauty", func (db *gorm.DB) *gorm.DB {
// ...
}).Find(&dog)
Has One
介绍
Has One与另一个模型建立一对一的关联,但它和一对一关系有些许不同。 这种关联表明一个模型的每个实例都包含或拥有另一个模型的一个实例。
示例
具有Has One模型示例如下:
// Has One模型,美女拥有一只舔狗,舔狗有一个标识拥有者的外键(美女ID)
type Dog struct {
gorm.Model
Name string
BeautyID uint
}
type Beauty struct {
gorm.Model
Name string
Dog dog
}
// Belongs To模型,舔狗有美女ID(外键),并且舔狗属于一个美女
type Dog struct {
gorm.Model
Name string
BeautyId int
Beauty Beauty
}
type Beauty struct {
gorm.Model
Name string
}
区别和共同点:
- 首先两种模型被拥有者都有一个拥有者的外键。
- 两种模型展在不同的视角,Belongs To站在被拥有者的视角,所以有一个拥有者的数据;Has One站在拥有者的视角,所以存在一个被拥有者的数据。
与Belongs To类似,Has One可以重写定义的外键和引用。(再温习一下,外键默认名是“拥有者+ID”,默认引用的是拥有者的ID值)
type Dog struct {
gorm.Model
Name string
BeautyName string
}
// 重写外键
type Beauty struct {
gorm.Model
Name string
Dog Dog `gorm:"foreignKey:BeautyName"`
}
// 重写引用
type Dog struct {
gorm.Model
Name string
BeautyID uint
}
type Beauty struct {
gorm.Model
Name string
Dog Dog `gorm:"foreignKey:BeautyName;references:name"`
}
多态关联
GORM为Has One和Has Many提供了多态关联支持,它会将拥有者实体的表名、主键都保存到多态类型的字段中。
type Dog struct {
ID int
Name string
Toys []Toy `gorm:"polymorphic:Owner;"`
}
type Toy struct {
ID int
Name string
OwnerID int
OwnerType string
}
db.Create(&Dog{Name: "dog1", Toys: []Toy{{Name: "toy1"}, {Name: "toy2"}}})
// INSERT INTO `dogs` (`name`) VALUES ("dog1")
// INSERT INTO `toys` (`name`,`owner_id`,`owner_type`) VALUES ("toy1","1","dogs"), ("toy2","1","dogs")
Has Many
介绍
Has Many就是一对多的连接,对比Has One,拥有者可以有零个或者多个关联模型。
示例
// Has Many
type Dog struct {
gorm.Model
Name string
BeautyID uint
}
type Beauty struct {
gorm.Model
Name string
Dogs []Dog
}
// Has One
type Dog struct {
gorm.Model
Name string
BeautyID uint
}
type Beauty struct {
gorm.Model
Name string
Dog Dog
}
其他的部分和Has One等类似,忽略介绍。
Many To Many
Many to Many会在两个model中添加一张连接表。当使用GORM的AutoMigrate
为Dog/Beauty创建表时,GORM会自动创建连接表
type Dog struct {
gorm.Model
Name string
Beauties []*Beauty `gorm:"many2many:dogs_beauties"`
// 表名为dogs_beauties,必须自行设置
}
type Beauty struct {
gorm.Model
Name string
Dogs []*Dog `gorm:"many2many:dogs_beauties"`
}
创建的连接表只保存两者的ID,关系信息保存在这个连接表中,其他操作与上文类似,不再赘述。
关联操作
// 开始关联模式
db.Model(&dog).Association("Beauty")
// 查找关联
db.Model(&dog).Association("Beauty").Find(&beauty)
// 添加关联(为many to many、has many添加新的关联;为has one, belongs to替换当前的关联)
db.Model(&dog).Association("Beauty").Append(&beauty)
// 替换关联
db.Model(&dog).Association("Beauty").Replace(&beauty)
// 删除关联(只会删除引用,不会从数据库中删除这些对象)
db.Model(&dog).Association("Beauty").Delete(&beauty)
// 清空关联(删除所有引用)
db.Model(&dog).Association("Beauty").Clear()
// 关联计数
db.Model(&dog).Association("Beauty").Count()