3、迁移模型定义:结构体标签详解

在 GORM 自动迁移中,Go 结构体标签是定义数据库表结构的核心手段。通过 gorm 标签,开发者可以精确控制字段的列名、数据类型、约束、索引、默认值等属性,无需手写 DDL 语句。本章将逐一拆解最常用的 8 个标签参数,并结合实战场景说明其用法与避坑点。

3.1 标签语法与基本结构

GORM 结构体标签写在字段的 gorm 键中,多个参数用分号分隔,参数值用冒号赋值。标签内不允许出现空格(除非值本身包含空格,如 default 的字符串值)。

type User struct {
    ID        uint   `gorm:"column:id;primaryKey;autoIncrement"`
    Name      string `gorm:"column:name;type:varchar(100);not null;default:'guest';comment:'用户名'"`
    Age       int    `gorm:"column:age;type:int;size:3;default:18"`
    Email     string `gorm:"column:email;unique;not null"`
    CreatedAt time.Time
}
💡 标签顺序无关:GORM 解析标签时按参数名匹配,不依赖书写顺序。但建议按 column → type → size → 约束 → default → comment 的惯例排列,提升可读性。

3.2 核心标签参数详解

下表列出 8 个最常用的标签参数及其作用域、数据类型支持与迁移行为:

标签参数 作用 支持的数据类型 迁移行为
column 指定数据库列名 所有类型 自动迁移时按此名称建列;不指定则使用蛇形字段名
type 指定数据库列的数据类型 所有类型 覆盖 GORM 默认类型推断;需与数据库方言兼容
size 指定列长度/精度 整型、字符串、decimal 对整型影响显示宽度;对字符串影响最大字符数
primaryKey 标记为主键 所有类型 创建主键约束;支持复合主键
unique 创建唯一约束 所有类型 生成唯一索引;可配合 uniqueIndex 指定索引名
default 设置列的默认值 所有类型 迁移时写入 DDL 的 DEFAULT 子句;注意字符串需加引号
not null 禁止 NULL 值 所有类型 迁移时添加 NOT NULL 约束
comment 添加列注释 所有类型 迁移时写入 COMMENT 子句;MySQL 等方言支持

3.3 标签参数实战详解

3.3.1 column:显式控制列名

默认情况下,GORM 将字段名转为蛇形命名作为列名(如 CreatedAt → created_at)。当需要自定义列名(如保留大小写、使用缩写、兼容遗留表)时,使用 column 标签。

type Product struct {
    ProductID   uint   `gorm:"column:product_id;primaryKey"`
    ProductName string `gorm:"column:productName"`  // 保留驼峰
    SKU         string `gorm:"column:sku_code"`     // 自定义缩写
}
⚠️ 注意:column 只影响列名,不影响字段与列的映射关系。如果同时使用 column 和 embedded 标签,需确保列名在嵌入结构中不冲突。

3.3.2 type:精确控制数据类型

GORM 根据 Go 类型自动推断数据库类型(如 string → varchar(255),int → bigint)。当需要特定类型(如 text、decimal(10,2)、json)时,使用 type 标签覆盖。

type Article struct {
    Content  string  `gorm:"type:text"`                    // 长文本
    Price    float64 `gorm:"type:decimal(10,2)"`           // 精确小数
    Metadata string  `gorm:"type:json"`                    // JSON 类型(MySQL 5.7+)
    Status   int8    `gorm:"type:tinyint;default:0"`       // 小整数
}
📌 方言兼容:type 值直接拼入 DDL,不同数据库方言需使用对应类型名。例如 PostgreSQL 使用 jsonb 而非 json,SQLite 不支持 json 类型。建议在迁移前通过 db.Migrator().ColumnTypes() 验证。

3.3.3 size:控制长度与精度

size 参数的行为因数据类型而异:

  • 整型:设置显示宽度(如 int(3)),不影响存储范围。MySQL 8.0.17+ 已废弃显示宽度,但仍可被 GORM 解析。
  • 字符串:设置最大字符数(如 varchar(100))。
  • decimal:设置总位数(精度),需配合 type:decimal(10,2) 使用,此时 size 被忽略。
type Config struct {
    MaxUsers int    `gorm:"type:int;size:5"`          // int(5)
    Title    string `gorm:"size:200"`                 // varchar(200)
    Ratio    float64 `gorm:"type:decimal(8,4)"`       // decimal(8,4),size 无效
}

3.3.4 primaryKey:主键定义

标记字段为主键。GORM 默认使用 ID 字段作为主键,但通过 primaryKey 标签可以指定任意字段。支持复合主键:

type OrderItem struct {
    OrderID   uint `gorm:"primaryKey;column:order_id"`
    ProductID uint `gorm:"primaryKey;column:product_id"`
    Quantity  int
}

复合主键在迁移时会生成联合主键约束,且 GORM 自动禁用 autoIncrement(除非显式指定)。

3.3.5 unique:唯一约束

为字段创建唯一索引。当需要自定义索引名时,使用 uniqueIndex 替代:

type User struct {
    Email    string `gorm:"unique"`                    // 自动生成索引名
    Username string `gorm:"uniqueIndex:idx_username"`  // 指定索引名
}
💡 联合唯一:如需多字段联合唯一,使用 uniqueIndex 标签并指定相同索引名:gorm:"uniqueIndex:idx_pair" 写在多个字段上。

3.3.6 default:默认值

设置列的默认值。注意以下规则:

  • 字符串:必须用单引号包裹,如 default:'active'。
  • 数字:直接写值,如 default:0、default:18.5。
  • 布尔:使用 true 或 false(小写)。
  • 时间:使用数据库函数,如 default:CURRENT_TIMESTAMP(不加引号)。
  • NULL:使用 default:NULL(大写)。
type User struct {
    Status    string    `gorm:"default:'active'"`              // 字符串默认值
    Age       int       `gorm:"default:18"`                    // 数字默认值
    IsAdmin   bool      `gorm:"default:false"`                 // 布尔默认值
    CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP"`     // 函数默认值
    DeletedAt gorm.DeletedAt `gorm:"default:NULL"`             // NULL 默认值
}
⚠️ 零值问题:Go 的零值(如 0、""、false)在插入时会被 GORM 视为“未设置”,从而使用数据库默认值。如果业务上需要显式插入零值,需使用指针类型或 sql.NullXxx。

3.3.7 not null:非空约束

禁止列存储 NULL 值。迁移时生成 NOT NULL 约束。注意:

  • 如果字段同时有 default 标签,插入时若未指定值则使用默认值,不会违反非空约束。
  • 如果字段没有默认值且未赋值,GORM 会写入 Go 零值(如 0、""),而非 NULL,因此不会触发约束错误。
type Account struct {
    Username string `gorm:"not null;unique;size:50"`
    Password string `gorm:"not null;size:255"`
}

3.3.8 comment:列注释

为列添加描述性注释,便于数据库维护和文档生成。注释内容在迁移时写入 COMMENT 子句(MySQL、PostgreSQL 等支持)。

type Employee struct {
    ID     uint   `gorm:"comment:'员工唯一标识'"`
    Name   string `gorm:"comment:'员工姓名,不超过50字'"`
    Salary float64 `gorm:"type:decimal(12,2);comment:'月薪,单位元'"`
}
📌 查看注释:迁移后可通过 SHOW FULL COLUMNS FROM table_name(MySQL)或 pg_catalog 系统表查看注释。

3.4 标签组合实战:完整模型示例

以下是一个综合使用上述标签的订单模型:

type Order struct {
    ID          uint        `gorm:"primaryKey;autoIncrement;comment:'订单ID'"`
    OrderNo     string      `gorm:"column:order_no;type:varchar(32);unique;not null;comment:'订单编号'"`
    UserID      uint        `gorm:"column:user_id;not null;index;comment:'用户ID'"`
    TotalAmount float64     `gorm:"type:decimal(12,2);not null;default:0.00;comment:'订单总金额'"`
    Status      int8        `gorm:"type:tinyint;default:0;comment:'订单状态:0-待支付 1-已支付 2-已取消'"`
    Note        string      `gorm:"type:text;default:NULL;comment:'订单备注'"`
    CreatedAt   time.Time   `gorm:"default:CURRENT_TIMESTAMP;comment:'创建时间'"`
    UpdatedAt   time.Time   `gorm:"default:CURRENT_TIMESTAMP;autoUpdateTime;comment:'更新时间'"`
    DeletedAt   gorm.DeletedAt `gorm:"index;default:NULL;comment:'删除时间'"`
}

3.5 标签参数关系图

下图展示了结构体标签与数据库表列定义之间的映射关系:

结构体标签 → 数据库列定义映射 Go 结构体字段 Name string `gorm:"column:name; type:varchar(100); not null; default:'guest'; comment:'用户名'"` GORM 标签解析 分号分割 → 键值对 数据库表列定义 列名 name 数据类型 varchar(100) 约束 NOT NULL 默认值 'guest' 注释 用户名 生成 DDL 每个标签参数对应数据库列的一个属性,GORM 在 AutoMigrate 时将其拼接为完整的 CREATE TABLE 语句

3.6 常见陷阱与最佳实践

陷阱场景 错误示例 正确做法
字符串默认值未加引号 default:active default:'active'
布尔默认值使用大写 default:TRUE default:true
时间默认值使用字符串 default:'CURRENT_TIMESTAMP' default:CURRENT_TIMESTAMP
复合主键未全部标记 只标记一个字段为 primaryKey 所有参与复合主键的字段都加 primaryKey
忽略零值问题 使用 int 字段且默认值为 0 使用 *int 或 sql.NullInt64
📌 最佳实践清单:
  • 所有业务字段都添加 comment 标签,便于后期维护。
  • 字符串字段显式指定 size,避免默认 255 字符过长或过短。
  • 金额字段使用 decimal 类型,避免浮点精度误差。
  • 枚举状态字段使用 tinyint 并配合 comment 说明枚举含义。
  • 逻辑删除字段 DeletedAt 务必添加 index 标签,提升查询性能。

3.7 标签与迁移的边界

结构体标签只影响 表的创建 和 新增列,不会修改已有列的类型或约束。GORM AutoMigrate 的边界规则如下:

  • 新增字段:自动添加列,使用标签定义。
  • 修改字段:不会修改已有列的类型、约束或默认值。
  • 删除字段:不会删除已有列。
  • 索引变更:新增索引会创建,但不会删除旧索引。

因此,标签定义是“前向兼容”的——生产环境表结构变更需通过手动迁移脚本完成,不能依赖 AutoMigrate 的标签修改。

⚠️ 生产环境警告:AutoMigrate 仅适用于开发/测试环境。生产环境的表结构变更(如修改列类型、添加非空约束、删除列)必须使用版本化迁移工具(如 gormigrate、golang-migrate)或手写 DDL 脚本。