多对多关系

多对多关系需要中间表。

例如:

一篇文章可以有多个标签
一个标签可以属于多篇文章

数据库里通常是:

articles
tags
article_tags

一、定义模型

type Article struct {
    ID        uint      `gorm:"primaryKey"`
    Title     string    `gorm:"size:200;not null"`
    Content   string    `gorm:"type:text;not null"`
    Tags      []Tag     `gorm:"many2many:article_tags;"`
    CreatedAt time.Time
    UpdatedAt time.Time
}

type Tag struct {
    ID        uint      `gorm:"primaryKey"`
    Name      string    `gorm:"size:50;not null;uniqueIndex"`
    CreatedAt time.Time
    UpdatedAt time.Time
}

关键标签:

gorm:"many2many:article_tags;"

表示 ArticleTag 通过 article_tags 表关联。

二、迁移

err := db.AutoMigrate(&Article{}, &Tag{})

GORM 会尝试创建:

  • articles
  • tags
  • article_tags

三、创建文章和标签

tag1 := Tag{Name: "GORM"}
tag2 := Tag{Name: "Go"}

article := Article{
    Title:   "GORM 多对多",
    Content: "多对多关系示例",
    Tags:    []Tag{tag1, tag2},
}

err := db.Create(&article).Error

GORM 会创建文章、标签和中间表关系。

本地练习时这样写比较方便,但真实项目里标签通常要先查是否存在,避免重复创建。

四、给已有文章添加标签

先查文章和标签:

var article Article
var tag Tag

db.First(&article, 1)
db.First(&tag, 1)

添加关联:

err := db.Model(&article).Association("Tags").Append(&tag)

五、删除关联

只删除文章和标签的关系,不删除标签本身:

err := db.Model(&article).Association("Tags").Delete(&tag)

清空文章所有标签关系:

err := db.Model(&article).Association("Tags").Clear()

六、查询文章和标签

var article Article

err := db.
    Preload("Tags").
    First(&article, 1).Error

遍历标签:

for _, tag := range article.Tags {
    fmt.Println(tag.Name)
}

七、按标签查询文章

可以用 Joins

var articles []Article

err := db.
    Joins("JOIN article_tags ON article_tags.article_id = articles.id").
    Joins("JOIN tags ON tags.id = article_tags.tag_id").
    Where("tags.name = ?", "GORM").
    Find(&articles).Error

复杂列表查询直接写清楚 SQL 关系,通常比强行使用关联 API 更容易理解。

八、中间表命名

默认中间表字段通常是:

article_id
tag_id

如果表名和字段名不符合 GORM 默认约定,需要额外配置外键标签。

新项目建议尽量遵循默认约定,减少配置。

九、多对多建议

场景建议
简单绑定Association("Tags").Append
删除绑定Association("Tags").Delete
查询详情带标签Preload("Tags")
按标签筛文章Joins 更直观
标签不能重复tags.name 加唯一索引