2. GORM 模型标签基础:gorm 标签语法、常用标签与作用域

GORM 通过结构体标签(Struct Tag)将 Go 结构体字段映射到数据库表的列。掌握标签语法与常用标签,是编写契约校验规则的前提。本章从标签语法入手,逐一拆解 column、type、size、primaryKey 等核心标签,并说明标签的作用域与优先级。

2.1 gorm 标签语法

GORM 标签写在结构体字段的 gorm 命名空间内,多个标签用分号分隔,标签名与值之间用冒号连接。基本格式如下:

type User struct {
    ID   uint   `gorm:"column:id;type:bigint;primaryKey"`
    Name string `gorm:"column:name;type:varchar(100);size:100;not null"`
}

标签语法要点:

  • 命名空间:必须使用 gorm:"...",其他命名空间(如 json、validate)不会被 GORM 解析。
  • 分隔符:多个标签之间使用英文分号 ; 分隔,最后一个标签后不加分号。
  • 键值对:标签名与值之间用英文冒号 : 连接,值不需要引号包裹。
  • 布尔标签:部分标签(如 primaryKey、autoIncrement)只写标签名即可,不需要值。
  • 大小写敏感:标签名对大小写不敏感,但建议统一使用小写驼峰或全小写。
💡 提示:GORM 标签与 JSON 标签可以共存,互不干扰。例如:gorm:"column:user_name" json:"userName"。

2.2 常用标签详解

下表列出 GORM 最常用的标签及其作用:

标签名 作用 示例 说明
column 指定数据库列名 column:user_id 默认使用字段名的蛇形命名
type 指定列的数据类型 type:varchar(255) 覆盖 GORM 自动推断的类型
size 指定列长度(仅对部分类型有效) size:100 常用于 varchar、int 等类型
primaryKey 标记为主键 primaryKey 布尔标签,无需值
autoIncrement 自增 autoIncrement 布尔标签,通常与主键配合
not null 非空约束 not null 布尔标签
unique 唯一约束 unique 布尔标签
default 默认值 default:0 值可以是数字、字符串或表达式
comment 列注释 comment:用户ID 仅用于建表/迁移
index 创建索引 index:idx_name 可指定索引名称

2.2.1 column 标签

column 标签用于显式指定数据库列名。如果不写,GORM 默认将 Go 字段名转换为蛇形命名(如 UserName → user_name)。当需要与现有表结构对齐或使用非标准命名时,必须使用 column 标签。

type Product struct {
    ProductID   uint   `gorm:"column:product_id"`
    ProductName string `gorm:"column:product_name"`
}

2.2.2 type 标签

type 标签直接指定数据库列的数据类型,包括长度和精度。GORM 会根据 Go 类型自动推断(如 string → varchar(255)),但使用 type 标签可以精确控制:

type Order struct {
    Amount    float64 `gorm:"type:decimal(10,2)"`
    Status    string  `gorm:"type:enum('pending','paid','shipped')"`
    CreatedAt time.Time `gorm:"type:datetime(3)"`
}
⚠️ 注意:type 标签会完全覆盖 GORM 的自动类型推断。如果写错类型,可能导致迁移失败或运行时错误。建议仅在需要特殊类型(如 enum、json、decimal)时使用。

2.2.3 size 标签

size 标签用于指定列的长度,主要影响 varchar、int 等可变长度类型。当与 type 标签同时存在时,size 会追加到类型定义中:

type Article struct {
    Title   string `gorm:"type:varchar;size:200"`   // 生成 varchar(200)
    Content string `gorm:"size:500"`                 // 生成 varchar(500)
    Views   int    `gorm:"size:10"`                  // 生成 int(10)
}

如果 type 标签已经包含长度(如 type:varchar(200)),则 size 标签会被忽略。

2.2.4 primaryKey 标签

primaryKey 是一个布尔标签,标记该字段为主键。GORM 默认将名为 ID 的字段视为主键,但使用 primaryKey 标签可以显式指定任意字段为主键:

type Employee struct {
    EmpNo    uint   `gorm:"primaryKey"`
    FullName string
}

支持复合主键:

type Membership struct {
    UserID    uint `gorm:"primaryKey"`
    GroupID   uint `gorm:"primaryKey"`
    JoinedAt  time.Time
}

2.3 标签作用域

GORM 标签的作用域决定了标签在哪些操作中生效。理解作用域有助于避免标签冲突和意外行为。

下表按操作类型划分标签作用域:

操作类型 生效标签 不生效标签
建表/迁移(AutoMigrate) column、type、size、primaryKey、autoIncrement、not null、unique、default、comment、index 无
CRUD 读写 column、primaryKey、autoIncrement type、size、not null、unique、default、comment、index
查询条件构建 column、primaryKey 其他所有标签
关联预加载 foreignKey、references、constraint 列类型相关标签
📌 关键结论:column 和 primaryKey 是全局生效的标签,在所有操作中都会被解析。type、size、not null 等约束类标签仅在 AutoMigrate 和 Create 表时生效,不影响已有表的读写操作。

2.4 标签优先级与覆盖规则

当多个标签对同一属性产生冲突时,GORM 按以下优先级处理:

  1. 显式标签优先:结构体标签中的值始终优先于 GORM 默认推断。
  2. 后写覆盖前写:同一标签多次出现时,最后一个生效。
  3. type 覆盖 size:如果 type 标签已包含长度信息,size 标签被忽略。
  4. 字段名 vs column:如果未写 column 标签,GORM 使用字段名的蛇形命名;写了则完全使用 column 指定的名称。
// 示例:标签覆盖
type Config struct {
    Key   string `gorm:"column:config_key;column:cfg_key;size:50;size:100"`
    // 最终 column = "cfg_key"(后写覆盖),size = 100(后写覆盖)
}

2.5 标签与契约校验的关系

在《GORM 契约校验》课程中,标签是定义字段约束的第一道防线。通过 not null、unique、default、size 等标签,可以在模型层声明数据契约,配合后续的校验器实现“定义即校验”。

下图展示了标签在契约校验流程中的位置:

GORM 标签在契约校验中的角色 结构体模型 gorm 标签定义 标签解析器 提取约束规则 契约规则集 not null / unique / size 校验执行器 运行时校验 数据库表 列约束生效 标签定义即契约 减少运行时校验代码

2.6 常见陷阱与最佳实践

  • 陷阱 1:type 与 size 混用导致类型重复——避免同时写 type:varchar(200) 和 size:200,只选其一。
  • 陷阱 2:忘记写 column 标签导致列名不符合预期——如果数据库已有表且列名非蛇形,务必显式指定 column。
  • 陷阱 3:primaryKey 标签与 autoIncrement 不匹配——主键自增需要同时写 primaryKey 和 autoIncrement,缺一不可。
  • 最佳实践 1:所有字段都显式写出 column 标签,提高可读性。
  • 最佳实践 2:将标签按逻辑分组排列,先写 column,再写 type/size,最后写约束标签。
  • 最佳实践 3:使用 comment 标签为关键字段添加说明,便于团队协作。
// 推荐写法示例
type User struct {
    ID        uint      `gorm:"column:id;type:bigint;primaryKey;autoIncrement;comment:用户ID"`
    Username  string    `gorm:"column:username;type:varchar;size:50;not null;unique;comment:用户名"`
    Email     string    `gorm:"column:email;type:varchar;size:100;not null;unique;comment:邮箱"`
    Age       int       `gorm:"column:age;type:int;size:3;default:0;comment:年龄"`
    CreatedAt time.Time `gorm:"column:created_at;type:datetime;comment:创建时间"`
}