6.gorm基础-多表关系(超重要)

在学习mysql多表关系的时候

我们都知道有一对一关系,一对多关系,多对多关系

我们不按照gorm的BelongsTo和Has Many去理解

而是从实际场景去理解

一对一关系

用户与用户详情是很明显的一对一关系

那么需要怎么表示这种一对一关系呢

在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)
fmt.Println(userDetail.Email, userDetail.UserModel.Name)

重点是Preload预加载

删除数据

针对多表关系下的删除

情况就有所不同了

一般会涉及到关联删除

例如用户和用户详情,如果用户被删除了,那么用户详情就没必要留着了,需要级联删除

  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}})

多对多关系

生活中多对多关系就更多了

文章和标签

书和作者

舔狗和女神

多对多关系就必须借助第三张表来进行关联

例如文章和标签

idarticle_idtag_id
111
212
321

很容易看出来,文章1有两个标签,分别是1和2,文章2有一个标签是1

所以在gorm中,如何实现呢?

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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值