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
}
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 默认值
}
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 标签参数关系图
下图展示了结构体标签与数据库表列定义之间的映射关系:
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 的标签修改。
gormigrate、golang-migrate)或手写 DDL 脚本。