多对多关系

多对多关系

多对多关系,需要用第三张表存储两张表的关系

表结构搭建

type Tag struct {
	ID       uint
	Name     string
	Articles []Article `gorm:"many2many:article_tag;"` // 用于反向引用
}

func (t Tag) TableName() string {
	return "tag"
}

type Article struct {
	ID    uint
	Title string
	Tags  []Tag `gorm:"many2many:article_tag;"` // 需要使用标签说明是多对多的关系,并指定第三张表的表名,会自动生成相应表名的表(不用管)
}

func (a Article) TableName() string {
	return "article"
}

/*
	TableName方法指定表名
*/

多对多添加

添加文章,并创建标签

DB.Create(&Article{
    Title: "Python基础",
    Tags: []Tag{
        {
            Name: "python",
        },
        {
            Name: "后端",
        },
    },
})

添加文章,选择已有标签

var tag []Tag
// 查找出已有的标签
DB.Take(&tag, "name in ?", []string{"后端"})
// 添加
DB.Create(&Article{
    Title: "Golang",
    Tags:  tag,
})

多对多查询

查询文章,显示文章的标签列表

article := Article{}
DB.Preload("Tags").Take(&article, 2)
fmt.Println(article)

查询标签,显示文章列表

tag := Tag{}
DB.Preload("Articles").Take(&tag, 2)
fmt.Println(tag)

多对多更新(先删除原有的,再添加新的)

移除文章所有的标签

article := Article{}
DB.Preload("Tags").Take(&article, 1) // 必须使用预加载 ,不然会删除失败
DB.Model(&article).Association("Tags").Delete(article.Tags)

添加文章标签

tag := Tag{}
DB.Take(&tag, 2)
DB.Model(&article).Association("Tags").Append(&tag)

更新文章的标签

// 先获取要更新的文章
article := Article{}
DB.Preload("Tags").Take(&article, 1)
// 获取要更新的标签
tag := Tag{}
DB.Take(&tag, 1)
// 更新 (会删除原来的所有,然后再添加)
DB.Model(&article).Association("Tags").Replace(&tag)

自定义连接表

默认的连接表,只有双方的主键id,展示不了更多信息了

这是官方的例子,我修改了一下

type Article struct {
  ID    uint
  Title string
  Tags  []Tag `gorm:"many2many:article_tags"`
}

type Tag struct {
  ID   uint
  Name string
}

type ArticleTag struct {
  ArticleID uint `gorm:"primaryKey"`
  TagID     uint `gorm:"primaryKey"`
  CreatedAt time.Time
}

生成表结构

// 设置Article的Tags表为ArticleTag
DB.SetupJoinTable(&Article{}, "Tags", &ArticleTag{})
// 如果tag要反向应用Article,那么也得加上
// DB.SetupJoinTable(&Tag{}, "Articles", &ArticleTag{})
err := DB.AutoMigrate(&Article{}, &Tag{}, &ArticleTag{})
fmt.Println(err)

操作案例

举一些简单的例子

  1. 添加文章并添加标签,并自动关联

  2. 添加文章,关联已有标签

  3. 给已有文章关联标签

  4. 替换已有文章的标签

  5. 添加文章并添加标签,并自动关联

DB.SetupJoinTable(&Article{}, "Tags", &ArticleTag{})  // 要设置这个,才能走到我们自定义的连接表
DB.Create(&Article{
  Title: "flask零基础入门",
  Tags: []Tag{
    {Name: "python"},
    {Name: "后端"}, 
    {Name: "web"},
  },
})
// CreatedAt time.Time 由于我们设置的是CreatedAt,gorm会自动填充当前时间,
// 如果是其他的字段,需要使用到ArticleTag 的添加钩子 BeforeCreateCopyErrorOK!
  1. 添加文章,关联已有标签
DB.SetupJoinTable(&Article{}, "Tags", &ArticleTag{})
var tags []Tag
DB.Find(&tags, "name in ?", []string{"python", "web"})
DB.Create(&Article{
  Title: "flask请求对象",
  Tags:  tags,
})CopyErrorOK!
  1. 给已有文章关联标签
DB.SetupJoinTable(&Article{}, "Tags", &ArticleTag{})
article := Article{
  Title: "django基础",
}
DB.Create(&article)
var at Article
var tags []Tag
DB.Find(&tags, "name in ?", []string{"python", "web"})
DB.Take(&at, article.ID).Association("Tags").Append(tags)CopyErrorOK!
  1. 替换已有文章的标签
var article Article
var tags []Tag
DB.Find(&tags, "name in ?", []string{"后端"})
DB.Take(&article, "title = ?", "django基础")
DB.Model(&article).Association("Tags").Replace(tags)CopyErrorOK!
  1. 查询文章列表,显示标签
var articles []Article
DB.Preload("Tags").Find(&articles)
fmt.Println(articles)CopyErrorOK!

SetupJoinTable

添加和更新的时候得用这个

这样才能走自定义的连接表,以及走它的钩子函数

查询则不需要这个

自定义连接表主键

这个功能还是很有用的,例如你的文章表 可能叫ArticleModel,你的标签表可能叫TagModel

那么按照gorm默认的主键名,那就分别是ArticleModelID,TagModelID,太长了,根本就不实用

这个地方,官网给的例子看着也比较迷,不过我已经跑通了

主要是要修改这两项

joinForeignKey 连接的主键id

JoinReferences 关联的主键id

type ArticleModel struct {
  ID    uint
  Title string
  Tags  []TagModel `gorm:"many2many:article_tags;joinForeignKey:ArticleID;JoinReferences:TagID"`
}

type TagModel struct {
  ID       uint
  Name     string
  Articles []ArticleModel `gorm:"many2many:article_tags;joinForeignKey:TagID;JoinReferences:ArticleID"`
}

type ArticleTagModel struct {
  ArticleID uint `gorm:"primaryKey"` // article_id
  TagID     uint `gorm:"primaryKey"` // tag_id
  CreatedAt time.Time
}CopyErrorOK!

生成表结构

DB.SetupJoinTable(&ArticleModel{}, "Tags", &ArticleTagModel{})
DB.SetupJoinTable(&TagModel{}, "Articles", &ArticleTagModel{})
err := DB.AutoMigrate(&ArticleModel{}, &TagModel{}, &ArticleTagModel{})
fmt.Println(err)CopyErrorOK!

添加,更新,查询操作和上面的都是一样

操作连接表

如果通过一张表去操作连接表,这样会比较麻烦

比如查询某篇文章关联了哪些标签

或者是举个更通用的例子,用户和文章,某个用户在什么时候收藏了哪篇文章

无论是通过用户关联文章,还是文章关联用户都不太好查

最简单的就是直接查连接表

type UserModel struct {
  ID       uint
  Name     string
  Collects []ArticleModel `gorm:"many2many:user_collect_models;joinForeignKey:UserID;JoinReferences:ArticleID"`
}

type ArticleModel struct {
  ID    uint
  Title string
  // 这里也可以反向引用,根据文章查哪些用户收藏了
}

// UserCollectModel 用户收藏文章表
type UserCollectModel struct {
  UserID    uint `gorm:"primaryKey"` // article_id
  ArticleID uint `gorm:"primaryKey"` // tag_id
  CreatedAt time.Time
}

func main() {
  DB.SetupJoinTable(&UserModel{}, "Collects", &UserCollectModel{})
  err := DB.AutoMigrate(&UserModel{}, &ArticleModel{}, &UserCollectModel{})
  fmt.Println(err)
}CopyErrorOK!

常用的操作就是根据用户查收藏的文章列表

var user UserModel
DB.Preload("Collects").Take(&user, "name = ?", "枫枫")
fmt.Println(user)CopyErrorOK!

但是这样不太好做分页,并且也拿不到收藏文章的时间

var collects []UserCollectModel
DB.Find(&collects, "user_id = ?", 2)
fmt.Println(collects)CopyErrorOK!

这样虽然可以查到用户id,文章id,收藏的时间,但是搜索只能根据用户id搜,返回也拿不到用户名,文章标题等

我们需要改一下表结构,不需要重新迁移,加一些字段

type UserModel struct {
  ID       uint
  Name     string
  Collects []ArticleModel `gorm:"many2many:user_collect_models;joinForeignKey:UserID;JoinReferences:ArticleID"`
}

type ArticleModel struct {
  ID    uint
  Title string
}

// UserCollectModel 用户收藏文章表
type UserCollectModel struct {
  UserID       uint         `gorm:"primaryKey"` // article_id
  UserModel    UserModel    `gorm:"foreignKey:UserID"`
  ArticleID    uint         `gorm:"primaryKey"` // tag_id
  ArticleModel ArticleModel `gorm:"foreignKey:ArticleID"`
  CreatedAt    time.Time
}CopyErrorOK!

查询

var collects []UserCollectModel

var user UserModel
DB.Take(&user, "name = ?", "枫枫")
// 这里用map的原因是如果没查到,那就会查0值,如果是struct,则会忽略零值,全部查询
DB.Debug().Preload("UserModel").Preload("ArticleModel").Where(map[string]any{"user_id": user.ID}).Find(&collects)

for _, collect := range collects {
  fmt.Println(collect)
}
posted @ 2024-01-24 11:33  春游去动物园  阅读(37)  评论(0编辑  收藏  举报