多表关系
1553字约5分钟
2025-02-15
一对一关系
用户与用户详情是很明显的一对一关系
那么需要怎么表示这种一对一关系呢
在mysql里面,需要在另一种表里面增加一个字段
例如 user表和user_detail表
那么就需要在user_detail表中增加一个user_id字段
或者在user表中新增user_detail_id字段
这个字段我们把它称之为外键字段
在一对一关系里面,外键字段随便在哪一张表里面都行
在gorm里面如何表示?
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var DB *gorm.DB
type UserModel struct {
ID int64
Name string `gorm:"size:32"`
Age int
}
type UserDetailModel struct {
ID int64
UserID int64
UserModel UserModel `gorm:"foreignKey:UserID"`
Email string `gorm:"size:64"`
}
func init() {
db, err := gorm.Open(mysql.Open("root:root@tcp(127.0.0.1:3306)/gorm_new_db?charset=utf8mb4&parseTime=True&loc=Local"), &gorm.Config{})
if err != nil {
fmt.Println(err)
return
}
DB = db
}
func migrate() {
err := DB.AutoMigrate(&UserModel{}, &UserDetailModel{})
if err != nil {
fmt.Println(err)
return
}
fmt.Println("表结构生成成功")
}
func main() {
migrate()
}
注意
这种方式会生成实体外键,它可以做一些数据约束
如果不需要生成实体外键,则可以进行如下配置
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true, // 不生成实体外键
})
那么针对这个表结构
UserModel 中的UserDetailModel 属于反向引用
UserDetailModel 中的UserModel 属于正向引用
type UserModel struct {
ID int64
Name string `gorm:"size:32"`
Age int
UserDetailModel *UserDetailModel `gorm:"foreignKey:UserID"`
}
type UserDetailModel struct {
ID int64
UserID int64
UserModel UserModel `gorm:"foreignKey:UserID"`
Email string `gorm:"size:64"`
}
插入数据
先插入user,再插入user_detail
DB.Create(&UserModel{
Name: "阿哲",
UserDetailModel: &UserDetailModel{
Email: "xxx@qq.com",
},
})
给已有的user插入user_detail
DB.Create(&UserDetailModel{
UserID: 2,
Email: "zzz@qq.com",
})
知道user,插入user_detail
var user UserModel
DB.Take(&user, 2)
DB.Create(&UserDetailModel{
UserModel: user,
Email: "zzz@qq.com",
})
查询数据
// 根据用户查用户详情
var user UserModel
DB.Preload("UserDetailModel").Take(&user, 2)
fmt.Println(user.Name, user.UserDetailModel.Email)
// 根据用户详情查用户
var userDetail UserDetailModel
DB.Preload("UserModel").Take(&userDetail, "user_id = ?", 2) //Preload预加载
fmt.Println(userDetail.Email, userDetail.UserModel.Name)
删除数据
例如用户和用户详情,如果用户被删除了,那么用户详情就没必要留着了,需要级联删除
var user UserModel
DB.Take(&user, 2)
DB.Select("UserDetailModel").Delete(&user)
但是还有些是不需要级联删除的,需要把对应的外键给设置为Null
var user UserModel
DB.Take(&user, 1)
DB.Delete(&user)
DB.Model(&user).Association("UserDetailModel").Clear()
这两种情况都是很常见的关联删除,可以有更简单的方式实现
type UserModel struct {
ID int64
Name string `gorm:"size:32"`
Age int
UserDetailModel *UserDetailModel `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"` // CASCADE 或者 SET NULL
}
注意
前提得生成实体外键才可以,而且修改关系之后要重新删掉实体外键再生成
一对多关系
用户与文章
班级与学生
type Girl struct {
ID int64
Name string `gorm:"size:32"`
BoyList []Boy `gorm:"foreignKey:GirlID"`
}
type Boy struct {
ID int64
GirlID int64
Name string `gorm:"size:32"`
Girl Girl `gorm:"foreignKey:GirlID"`
}
那么对应Girl来说,Body就是她拥有的,叫Has Many
对应Boy来说,Girl就是他的归属,叫Belongs To
插入数据
创建一个女神,几个男孩
DB.Create(&Girl{
Name: "娜娜",
BoyList: []Boy{
{Name: "张三"},
{Name: "王五"},
},
})
创建boy,然后自带女神
DB.Create(&Boy{
Name: "阿哲",
Girl: Girl{
Name: "露露",
},
})
创建Boy,然后关联已有女神
var girl Girl
DB.Take(&girl, "name = ?", "露露")
DB.Create(&Boy{
Name: "李四",
Girl: girl,
})
关联查询
查女神,关联她的男孩也查出来
var girl Girl
DB.Preload("BoyList").Take(&girl, "name = ?", "露露")
fmt.Println(girl)
带条件的Preload
girl = Girl{}
DB.Preload("BoyList", "name = ?", "阿哲").Take(&girl, "name = ?", "露露")
fmt.Println(girl)
查女神的男孩列表
girl = Girl{}
DB.Take(&girl, "name = ?", "露露")
var bodyList []Boy
DB.Model(girl).Association("BoyList").Find(&bodyList)
fmt.Println(bodyList)
查女神的男孩总数
count := DB.Model(girl).Association("BoyList").Count()
fmt.Println(count)
关联操作
1,2号离开女神了,换成了3号
var b3 = Boy{
ID: 3,
}
girl := Girl{}
DB.Take(&girl, "name = ?", "露露")
DB.Model(&girl).Association("BoyList").Replace([]Boy{b3})
都离开女神了
girl := Girl{}
DB.Take(&girl, "name = ?", "露露")
DB.Model(&girl).Association("BoyList").Clear()
1,3号又开始找女神了
girl := Girl{}
DB.Take(&girl, "name = ?", "露露")
DB.Model(&girl).Association("BoyList").Append([]Boy{{ID: 1}, {ID: 3}})
只有3号离开了
girl := Girl{}
DB.Take(&girl, "name = ?", "露露")
DB.Model(&girl).Association("BoyList").Delete([]Boy{{ID: 3}})
多对多关系
文章和标签
书和作者
多对多关系需要借助第三张表来进行关联
例如文章和标签
type Article struct {
ID int64
Title string `gorm:"size:32"`
TagList []Tag `gorm:"many2many:article_tags;"`
}
type Tag struct {
ID int64
Title int64 `gorm:"size:32"`
ArticleList []Article `gorm:"many2many:article_tags;"`
}
插入数据
创建文章,并创建标签
article := Article{Title: "文章1", TagList: []Tag{
{Title: "go"},
{Title: "python"},
}}
DB.Create(&article)
创建文章,选择已有标签
tagIDList := []int64{1, 2}
var tagList []Tag
DB.Find(&tagList, "id in ?", tagIDList)
DB.Create(&Article{
Title: "文章2",
TagList: tagList,
})
关联查询
查文章列表,并把标签带出来
var articleList []Article
DB.Preload("TagList").Find(&articleList)
fmt.Println(articleList)
关联操作
更新一篇文章的标签列表
var article Article
DB.Take(&article, "title = ?", "文章1")
DB.Model(&article).Association("TagList").Replace([]Tag{
{ID: 1},
{Title: "后端"},
})
自定义第三张表
以用户收藏文章为例
就是用户与文章的多对多关系
如果用默认的第三张表,用户想知道什么时候收藏的某一篇文章,就没办法了
表结构
type UserModel struct {
ID int64
Name string
CollArticleList []ArticleModel `gorm:"many2many:user2_article_models;joinForeignKey:UserID;JoinReferences:ArticleID"`
}
type ArticleModel struct {
ID int64
Title string `gorm:"size:32"`
}
type User2ArticleModel struct {
UserID int64 `gorm:"primaryKey"`
UserModel UserModel `gorm:"foreignKey:UserID"`
ArticleID int64 `gorm:"primaryKey"`
ArticleModel ArticleModel `gorm:"foreignKey:ArticleID"`
CreatedAt time.Time `json:"createdAt"`
}
注意
重点是many2many:生成表的名称要和第三张表的名称要对上
然后joinForeignKey对应的是本表的ID,例如用户表的joinForeignKey就是UserID
JoinReferences对应的是对方表的ID,用户表的JoinReferences就是ArticleID
然后就是要添加SetupJoinTable,不然是不会走第三张表的创建钩子
// 必须要加这个才会走第三张表的创建钩子
DB.SetupJoinTable(&UserModel{}, "CollArticleList", &User2ArticleModel{})
然后创建,删除就和之前是一样的了
DB.Create(&UserModel{
Name: "张三",
CollArticleList: []ArticleModel{
{Title: "文章1"},
{Title: "文章2"},
},
})
var user UserModel
DB.Take(&user)
DB.Select("CollArticleList").Delete(&user)
查询
查询不能直接用Preload了,因为Preload出来的是对方表,是没有收藏时间的
所以常规操作是直接查第三张表
type UserCollArticleResponse struct {
Name string
UserID int64
ArticleTitle string
ArticleID int64
Date time.Time
}
var userID = 4
var userArticleList []models.User2ArticleModel
var collList []UserCollArticleResponse
global.DB.Preload("UserModel").Preload("ArticleModel").Find(&userArticleList, "user_id = ?", userID)
for _, model := range userArticleList {
collList = append(collList, UserCollArticleResponse{
Name: model.UserModel.Name,
UserID: model.UserID,
ArticleTitle: model.ArticleModel.Title,
ArticleID: model.ArticleID,
Date: model.CreatedAt,
})
}
fmt.Println(collList)