多对多关联模式只与前几种关联模式有少许区别,本文着重的利用Many2Many关系学习并实验关联模式的相关操作,包括建立关系,更换关系等等。
建立数据库连接
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"log"
)
var db *gorm.DB
func OpenDB() {
dsn := "root:adss123@tcp(127.0.0.1:3306)/go_db?charset=utf8mb4&parseTime=True&loc=Local"
res, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
db = res
if err != nil {
log.Fatal(err)
}
fmt.Printf("成功:%v\n", db)
}
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;"`
}
type Language struct {
gorm.Model
Name string
}
当使用 GORM 的 AutoMigrate 为 User 创建表时,GORM 会自动创建连接表
与其他关系中最大不同是,两个关系表中没有直接的外键字段来连接,而是通过 gorm:"many2many:tb1_tb2;"
标签,生成连接表,来创建联系。
实验案例如下
type Students struct {
gorm.Model
Name string
Classes []Classes `gorm:"many2many:students_classes;"`
}
type Classes struct {
gorm.Model
Name string
Students []Students `gorm:"many2many:students_classes;"`
}
func CreateMany2Many() {
OpenDB()
db.AutoMigrate(&Classes{})
}
发现在构造时,两个表相互嵌套的话,需要在每个嵌套字段后加上标签,不能单独在其中一个嵌套字段上加标签,另一个不加。
并且创建的时候,创建其中一个表,另一个表与连接表,都会自动创建。
利用Association()
方法建立两张表的关联,并通过Association下的方法经行关联操作。
利用Append
方法为 many to many、has many 添加新的关联;为 has one, belongs to 替换当前的关联
db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})
db.Model(&user).Association("Languages").Append(&Language{Name: "DE"})
db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"})
实验如下
首先创建两条记录,创建的过程省略,详细方法可以参考之前的利用gorm方法创建记录的笔记。
classes表
students表
students_classes表
此时两条信息未建立关系。
调用Association().Append()方法为两个记录创建关联。
func AssociationMany2Many() {
OpenDB()
class := &Classes{Model: gorm.Model{
ID: 1,
}}
stu := &Students{Model: gorm.Model{
ID: 1,
}}
db.Model(stu).Association("Classes").Append(class)
}
流程为建立id为1的student模型,在此模型下,连接Classes表,并与id为1的class记录创建模型。
在student表与class表上没有任何变化,但连接表students_classes表建立了一个关系记录
有了关系后,尝试通过student查找其关联的class
func QueryMany2Many() {
OpenDB()
stu := &Students{}
db.Preload("Classes").First(stu, 1)
fmt.Println(stu)
}
查找所有匹配的关联记录
db.Model(&user).Association("Languages").Find(&languages)
查找带条件的关联
codes := []string{"zh-CN", "en-US", "ja-JP"}
db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages)
db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages)
相当于只查询出目标记录的关联信息
实验如下
func QueryMany2Many() {
OpenDB()
stu := &Students{Model: gorm.Model{
ID: 1,
}}
class := &Classes{}
db.Model(stu).Association("Classes").Find(class)
fmt.Println(class)
}
添加条件测试
先为stu1增加一个Eng信息的记录关联
普通查询:
func QueryMany2Many() {
OpenDB()
stu := &Students{Model: gorm.Model{
ID: 1,
}}
class := &[]Classes{}
db.Model(stu).Association("Classes").Find(class)
fmt.Println(class)
}
增加条件后:
func QueryMany2Many() {
OpenDB()
stu := &Students{Model: gorm.Model{
ID: 1,
}}
class := &[]Classes{}
db.Model(stu).Where("id=2").Association("Classes").Find(class)
fmt.Println(class)
}
由此可以发现,where语句的条件是用来限制后面的Find方法的,而不是限制前面Model的建立的,展示的实验还不足以论证结果,但是本人私下尝试,删除构造的stu中的Id,改变where中的条件,让name指向stu中的name,等方法,均证明where条件是约束后面find方法,前面model的选择,似乎只能在创建对象的时候,指定好目标
用一个新的关联替换当前的关联
db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN})
db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN)
实验如下
如上文所述,现在stu1有两个关联的class,分别为math与eng,现在将增加一个class记录PE,将stu1的关联改为pe与eng。
func AssociationMany2Many() {
OpenDB()
stu := &Students{Model: gorm.Model{
ID: 1,
}}
db.Model(stu).Association("Classes").Replace(&Classes{Name: "PE"})
}
与预期结果不符,发现如果Replace中只有一个参数的时候,方法会直接将所有关系全部替换为目标参数
尝试,有没有可能添加两个参数,前一个参数为被替换目标,后一个参数为替换目标
func AssociationMany2Many() {
OpenDB()
stu := &Students{Model: gorm.Model{
ID: 1,
}}
db.Model(stu).Association("Classes").Replace(&Classes{Name: "math"}, &Classes{Name: "Eng"})
QueryMany2Many()
}
失败,结果是两个参数全部被替换进去,发现无法指定替换。
似乎只能通过,指定删除关联,指定添加关联,来达到指定替换关联功能。
如果存在,则删除源模型与参数之间的关系,只会删除引用,不会从数据库中删除这些对象。
db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN})
db.Model(&user).Association("Languages").Delete(languageZH, languageEN)
实验代码
删除连接方法
func AssociationMany2Many() {
OpenDB()
stu := &Students{Model: gorm.Model{
ID: 1,
}}
db.Model(stu).Association("Classes").Delete(&Classes{Model: gorm.Model{ID: 1}})
QueryMany2Many()
}
注重实验指定替换关联方法。
现在stu1有两个关联信息,想把eng更换为pe。通过delete与append方法联合实现。
func AssociationMany2Many() {
OpenDB()
stu := &Students{Model: gorm.Model{
ID: 1,
}}
db.Model(stu).Association("Classes").Delete(&Classes{Model: gorm.Model{ID: 2}})
db.Model(stu).Association("Classes").Append(&Classes{Model: gorm.Model{ID: 3}})
QueryMany2Many()
}
方法成功。
删除源模型与关联之间的所有引用,但不会删除这些关联
db.Model(&user).Association("Languages").Clear()
返回当前关联的计数
db.Model(&user).Association("Languages").Count()
// 条件计数
codes := []string{"zh-CN", "en-US", "ja-JP"}
db.Model(&user).Where("code IN ?", codes).Association("Languages").Count()
// 删除 user 时,也删除 user 的 account
db.Select("Account").Delete(&user)
// 删除 user 时,也删除 user 的 Orders、CreditCards 记录
db.Select("Orders", "CreditCards").Delete(&user)
// 删除 user 时,也删除用户所有 has one/many、many2many 记录
db.Select(clause.Associations).Delete(&user)
// 删除 users 时,也删除每一个 user 的 account
db.Select("Account").Delete(&users)