目前的问题是:在插入文章的同时直接传入包含多个 Tag 的切片,由于 GORM 在保存多对多关联时默认认为每个 Tag 都是新的记录,导致每次插入文章时相同的 Tag 都会重新创建。为了解决这个问题,我们需要确保在保存文章之前,先对每个 Tag 去重,也就是先查询数据库中是否已经存在该 Tag,如果存在则使用已有记录,否则再创建新记录。下面给出一种常见的做法,使用 GORM 的事务(Transaction)和 FirstOrCreate
方法来处理这些逻辑。
假设 Tag 模型类似于:
type Tag struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"uniqueIndex"` // 保证 tag 名称的唯一性
// 其它字段...
}
下面是修改后的示例代码:
func CreatePost(article Article) error {
db := sql.GetDB()
// 使用事务确保操作的原子性
return db.Transaction(func(tx *gorm.DB) error {
// 处理每个 Tag
for i, tag := range article.Tags {
var existingTag Tag
// 根据 Tag 的唯一字段(例如 Name)进行查找
if err := tx.Where("name = ?", tag.Name).First(&existingTag).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
// 如果没有找到,则创建新标签
if err := tx.Create(&tag).Error; err != nil {
return err
}
// 更新 article.Tags 中的数据为新创建的 tag
article.Tags[i] = tag
} else {
// 出现其它错误时,返回错误,事务会回滚
return err
}
} else {
// 找到了,使用已有的 tag 记录
article.Tags[i] = existingTag
}
}
// 创建文章,GORM 会自动将 article.Tags 中的 Tag 与 Article 关联(写入中间表)
if err := tx.Create(&article).Error; err != nil {
return err
}
return nil
})
}
说明
- 事务使用
使用db.Transaction
来确保整个操作的原子性,如果中间任一步出现错误,整个事务会回滚。 Tag 去重逻辑
对于每个传入的 Tag,我们先根据唯一字段(比如Name
)进行查找:- 如果找不到则使用
tx.Create(&tag)
新建标签; - 如果找到了,则直接使用已存在的标签,避免重复插入。
- 如果找不到则使用
- 自动建立关联
当调用tx.Create(&article)
时,GORM 会自动处理多对多关系,将article.Tags
中的 Tag 信息写入关联表article_tags
。注意这要求你的Article
模型中所定义的多对多标签gorm:"many2many:article_tags;"
是正确设置的。
通过这种方式,每次插入文章时,相同的 Tag 都只会在数据库中保留一条记录,而只是在关联表中添加一条关联记录。这样既避免了数据冗余,也能保持多对多关系的正确性。