Gorm(七)关联的Tag写法
在 Gorm 中,关联关系(一对一、一对多、多对多)通过结构体字段和特定标签定义,配合 Association() 方法可实现关联数据的新增、替换、删除等操作。以下是详细说明:
一、关联关系的 Tag 写法
Gorm 通过 gorm:"foreignKey:xxx"、gorm:"references:xxx" 等标签定义关联关系的外键和引用键,默认规则如下:
- 外键名默认:
关联结构体名+ID(如User的外键为user_id)。 - 引用键默认:被关联结构体的主键(如
ID)。
1. 一对一(Has One / Belongs To)
场景:一个用户有一个身份证(User → IDCard)。
Has One:主表(User)关联从表(IDCard),从表包含外键(user_id)。Belongs To:从表(IDCard)属于主表(User),显式外键关联。
// 主表:User(一个用户有一个身份证)
type User struct {gorm.ModelName stringIDCard IDCard `gorm:"foreignKey:UserID"` // 关联 IDCard,指定外键为 UserID
}// 从表:IDCard(属于一个用户)
type IDCard struct {gorm.ModelNumber stringUserID uint // 外键:关联 User 的 IDUser User `gorm:"references:ID"` // 引用 User 的 ID 字段(可省略,默认引用主键)
}
foreignKey:UserID:指定从表的外键字段为UserID(默认会自动推断,显式指定更清晰)。references:ID:指定被关联表的引用键(默认为主键ID,可省略)。
2. 一对多(Has Many / Belongs To)
场景:一个用户有多个订单(User → Orders)。
Has Many:主表(User)有多个从表记录(Order),从表包含外键(user_id)。Belongs To:从表(Order)属于主表(User)。
// 主表:User(一个用户有多个订单)
type User struct {gorm.ModelName stringOrders []Order `gorm:"foreignKey:UserID"` // 一对多关联 Orders,外键为 UserID
}// 从表:Order(属于一个用户)
type Order struct {gorm.ModelProduct stringUserID uint // 外键:关联 User 的 IDUser User `gorm:"references:ID"` // 属于 User(可省略)
}
- 主表用切片字段(
[]Order)表示“多个”关联记录。 - 外键
UserID存储在从表Order中,关联主表User的ID。
3. 多对多(Many To Many)
场景:一个用户可以有多个角色,一个角色可以属于多个用户(User ↔ Role)。
多对多需要中间表(默认表名:user_roles,按结构体名复数排序)存储关联关系,中间表包含两个外键(如 user_id 和 role_id)。
// 主表1:User
type User struct {gorm.ModelName stringRoles []Role `gorm:"many2many:user_roles;foreignKey:ID;joinForeignKey:UserID;References:ID;joinReferences:RoleID"`
}// 主表2:Role
type Role struct {gorm.ModelName stringUsers []User `gorm:"many2many:user_roles"` // 反向关联,可复用中间表配置
}
标签参数说明:
many2many:user_roles:指定中间表名(默认是两个结构体名复数的拼接,如users_roles,显式指定更灵活)。foreignKey:ID:当前表(User)的关联键(默认主键ID)。joinForeignKey:UserID:中间表中关联当前表的外键(如user_id)。References:ID:被关联表(Role)的引用键(默认主键ID)。joinReferences:RoleID:中间表中关联被关联表的外键(如role_id)。
二、关联操作:Association().Append / Replace / Delete
通过 Association("关联字段名") 方法获取关联对象,再调用 Append、Replace、Delete 等方法操作关联数据。
1. Append:添加关联(不会删除现有关联)
向现有关联中添加新数据(多对多中常用,一对多中相当于新增子记录)。
示例(多对多:给用户添加角色):
// 1. 查询用户
var user User
db.First(&user, 1) // ID=1 的用户// 2. 准备要添加的角色(可以是已存在的角色)
var role1, role2 Role
db.First(&role1, 1) // ID=1 的角色
db.First(&role2, 2) // ID=2 的角色// 3. 给用户添加角色(中间表新增关联记录)
db.Model(&user).Association("Roles").Append(&role1, &role2)
示例(一对多:给用户添加订单):
// 给用户添加新订单(订单会自动设置 UserID 为用户 ID)
db.Model(&user).Association("Orders").Append(&Order{Product: "手机"}, &Order{Product: "电脑"})
2. Replace:替换关联(删除现有关联,仅保留新关联)
用新的关联数据替换现有关联(适合“全量更新”场景)。
示例(多对多:替换用户的角色):
// 替换用户的角色为 role3(原有关联会被删除)
var role3 Role
db.First(&role3, 3)
db.Model(&user).Association("Roles").Replace(&role3)
示例(一对多:替换用户的订单):
// 替换用户的订单(原订单不会被删除,仅解除关联,需手动删除旧订单)
newOrders := []Order{{Product: "平板"}, {Product: "手表"}}
db.Model(&user).Association("Orders").Replace(newOrders)
注意:一对多的
Replace只会更新外键(解除旧关联,建立新关联),不会删除旧记录,需手动删除。
3. Delete:删除部分关联
从现有关联中移除指定数据(多对多中删除中间表记录,一对多中解除外键关联)。
示例(多对多:移除用户的某个角色):
// 移除用户的 role1 角色(删除中间表中 user_id=1 和 role_id=1 的记录)
db.Model(&user).Association("Roles").Delete(&role1)
示例(一对多:解除用户与某个订单的关联):
// 解除用户与订单 order1 的关联(将订单的 UserID 设为 NULL)
var order1 Order
db.First(&order1, 10) // 用户的某个订单
db.Model(&user).Association("Orders").Delete(&order1)
三、关联操作的注意事项
- 关联字段必须存在:
Association("字段名")中的字段名必须是结构体中定义的关联字段(如Roles、Orders),否则会报错。 - 被关联记录需存在:
Append/Replace时,被关联的记录(如Role)必须已存在于数据库中(除非开启了级联创建)。 - 级联创建:若要在添加关联时自动创建被关联记录,需结合
Create并开启Preload:// 新增用户时同时创建关联的订单(级联创建) db.Create(&User{Name: "Alice",Orders: []Order{{Product: "耳机"}, {Product: "键盘"}}, }) - 中间表操作:多对多的关联操作仅影响中间表,不会删除主表(
User或Role)的记录。
总结
- 关联定义:通过
foreignKey、references、many2many等标签定义一对一、一对多、多对多关系,明确外键和中间表。 - 关联操作:
Append:添加关联,保留现有数据。Replace:替换关联,删除现有数据。Delete:移除部分关联,保留其他数据。
合理使用关联标签和 Association 方法,可高效处理复杂的关联关系。
