多表关系
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)