3、模型定义基础:结构体标签(gorm标签)、字段类型映射、主键与自增ID、表名约定
在 GORM 中,模型(Model)是 Go 结构体与数据库表之间的映射契约。本章将深入解析结构体标签(gorm 标签)的完整语法、Go 类型与数据库类型的映射规则、主键与自增 ID 的配置方式,以及表名的默认约定与自定义策略。掌握这些基础,是构建稳定、可维护的数据库迁移体系的前提。
3.1 结构体标签:gorm 标签语法
GORM 使用结构体标签(struct tag)来声明字段的数据库属性。标签格式为 gorm:"key1:value1;key2:value2",多个键值对之间用分号分隔。以下是最常用的标签键及其含义:
| 标签键 | 说明 | 示例 |
|---|---|---|
column |
指定数据库列名 | gorm:"column:user_name" |
type |
指定数据库列类型(如 varchar(100)、text、bigint) | gorm:"type:varchar(100)" |
size |
指定列大小(仅对某些类型有效) | gorm:"size:255" |
primaryKey |
标记为主键 | gorm:"primaryKey" |
autoIncrement |
启用自增(默认对整数主键生效) | gorm:"autoIncrement" |
unique |
创建唯一约束 | gorm:"unique" |
not null |
设置非空约束 | gorm:"not null" |
default |
设置默认值 | gorm:"default:0" |
comment |
添加列注释 | gorm:"comment:用户昵称" |
index |
创建索引(可指定索引名) | gorm:"index:idx_name" |
uniqueIndex |
创建唯一索引 | gorm:"uniqueIndex:uidx_email" |
embedded |
嵌入结构体(展平字段) | gorm:"embedded" |
embeddedPrefix |
嵌入字段的前缀 | gorm:"embeddedPrefix:addr_" |
- |
忽略该字段(不参与迁移和读写) | gorm:"-" |
最佳实践:建议在团队中统一标签书写顺序:column → type → size → primaryKey → autoIncrement → not null → default → comment → index。保持可读性,便于代码审查。
type User struct {
ID uint `gorm:"column:id;type:bigint;primaryKey;autoIncrement;not null;comment:主键ID"`
Name string `gorm:"column:name;type:varchar(100);not null;default:'';comment:用户名"`
Email string `gorm:"column:email;type:varchar(255);uniqueIndex;not null;comment:邮箱"`
Age int `gorm:"column:age;type:int;default:0;comment:年龄"`
CreatedAt time.Time `gorm:"column:created_at;type:datetime;not null;comment:创建时间"`
UpdatedAt time.Time `gorm:"column:updated_at;type:datetime;not null;comment:更新时间"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime;index;comment:软删除时间"`
}
3.2 字段类型映射:Go 类型 → 数据库类型
GORM 内置了一套 Go 类型到数据库类型的默认映射规则。理解这些映射,有助于在迁移时避免类型不匹配导致的错误。
| Go 类型 | 默认数据库类型 | 说明 |
|---|---|---|
bool |
TINYINT(1) | 布尔值映射为 0/1 |
int, int8, int16, int32, uint, uint8, uint16, uint32 |
INT | 根据位数自动选择合适大小 |
int64, uint64 |
BIGINT | 大整数类型 |
float32 |
FLOAT | 单精度浮点 |
float64 |
DOUBLE | 双精度浮点 |
string |
LONGTEXT 或 VARCHAR(255) | 无 size 标签时默认 LONGTEXT;有 size 时变为 VARCHAR(size) |
[]byte |
BLOB | 二进制数据 |
time.Time |
DATETIME(3) | 精确到毫秒 |
json.RawMessage |
JSON | 仅 MySQL 5.7+ / PostgreSQL |
gorm.DeletedAt |
DATETIME(3) | 软删除专用 |
类型覆盖技巧:当默认映射不符合需求时,使用 gorm:"type:varchar(50)" 或 gorm:"type:decimal(10,2)" 显式指定数据库类型。这在金额、状态码等精确控制场景中尤为重要。
type Product struct {
ID uint `gorm:"primaryKey;autoIncrement"`
Name string `gorm:"type:varchar(200);not null"`
Price float64 `gorm:"type:decimal(10,2);not null;default:0.00"`
Stock int `gorm:"type:int;not null;default:0"`
Desc string `gorm:"type:text"`
}
3.3 主键与自增 ID
GORM 默认将名为 ID 的字段作为主键,并自动启用自增(AutoIncrement)。但实际项目中,主键策略往往需要更精细的控制。
3.3.1 默认主键行为
type User struct {
ID uint // 自动识别为主键,且自增
Name string
}
3.3.2 自定义主键字段
使用 primaryKey 标签将任意字段标记为主键:
type Order struct {
OrderID string `gorm:"column:order_id;type:varchar(32);primaryKey;comment:订单号"`
UserID uint `gorm:"not null"`
Amount float64
}
3.3.3 复合主键
在结构体中标记多个 primaryKey 字段,GORM 会自动创建联合主键:
type Membership struct {
UserID uint `gorm:"primaryKey;comment:用户ID"`
GroupID uint `gorm:"primaryKey;comment:群组ID"`
Role string `gorm:"type:varchar(20);not null"`
JoinedAt time.Time
}
3.3.4 自增 ID 的显式控制
使用 autoIncrement 标签显式启用自增,或使用 autoIncrementIncrement 设置步长(需数据库支持):
type Article struct {
ID uint `gorm:"primaryKey;autoIncrement;autoIncrementIncrement:10"`
Title string `gorm:"not null"`
Content string `gorm:"type:text"`
}
注意:自增 ID 在分布式场景下存在冲突风险。对于分库分表或跨数据中心部署,建议使用雪花算法(Snowflake)或 UUID 作为主键,并关闭自增。
3.4 表名约定
GORM 默认将结构体名转换为复数形式作为表名。转换规则为:驼峰命名转下划线分隔,并自动复数化(如 User → users,OrderItem → order_items)。
3.4.1 默认表名示例
| 结构体名 | 默认表名 |
|---|---|
User |
users |
OrderItem |
order_items |
ProductCategory |
product_categories |
APIRequest |
a_p_i_requests(连续大写字母会被拆分) |
3.4.2 自定义表名:TableName 方法
为结构体实现 TableName() string 方法,可完全控制表名:
func (User) TableName() string {
return "t_user"
}
func (OrderItem) TableName() string {
return "order_item" // 不使用复数
}
3.4.3 全局表名前缀
在 GORM 配置中设置 TablePrefix,可为所有表添加统一前缀:
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: "tbl_", // 所有表名前缀
SingularTable: false, // 是否使用单数表名
},
})
3.4.4 禁用复数表名
设置 SingularTable: true 后,表名与结构体名完全一致(不自动复数化):
NamingStrategy: schema.NamingStrategy{
SingularTable: true, // User → user, OrderItem → order_item
}
领域边界建议:在微服务架构中,建议为每个领域服务设置独立的表前缀(如 order_、user_),避免跨服务表名冲突。同时,在领域内部保持表名与实体名的一致性,降低认知负载。
3.5 模型定义完整示例
综合以上知识点,以下是一个符合领域边界设计的完整模型示例:
package model
import (
"time"
"gorm.io/gorm"
)
// Order 订单实体 - 订单领域
type Order struct {
ID uint `gorm:"column:id;type:bigint;primaryKey;autoIncrement;not null;comment:订单ID"`
OrderNo string `gorm:"column:order_no;type:varchar(32);uniqueIndex;not null;comment:订单编号"`
UserID uint `gorm:"column:user_id;type:bigint;not null;index;comment:用户ID"`
TotalAmount float64 `gorm:"column:total_amount;type:decimal(12,2);not null;default:0.00;comment:订单总金额"`
Status int8 `gorm:"column:status;type:tinyint;not null;default:0;comment:订单状态(0-待支付 1-已支付 2-已取消)"`
CreatedAt time.Time `gorm:"column:created_at;type:datetime(3);not null;comment:创建时间"`
UpdatedAt time.Time `gorm:"column:updated_at;type:datetime(3);not null;comment:更新时间"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:datetime(3);index;comment:删除时间"`
}
// TableName 自定义表名
func (Order) TableName() string {
return "order_order"
}
3.6 模型定义核心流程
下图展示了从 Go 结构体到数据库表的完整映射流程,以及各环节的关键决策点:
3.7 常见陷阱与避坑指南
- 标签拼写错误:
primaryKey是驼峰写法,primarykey或primary_key均无效,GORM 会静默忽略。 - 自增与主键绑定:只有标记了
primaryKey的字段才能使用autoIncrement;非主键字段设置自增会被忽略。 - 表名冲突:多个结构体实现相同的
TableName()返回同一表名时,迁移会互相覆盖,导致数据丢失。 - 类型精度丢失:Go 的
float64映射为DOUBLE,在金额场景下应显式指定type:decimal(10,2)避免精度问题。 - 时间时区:
time.Time默认使用 UTC 存储,建议在数据库连接 DSN 中指定loc=Local或loc=Asia/Shanghai。
调试技巧:在开发阶段,开启 GORM 的 Logger 日志级别为 info,可以查看 AutoMigrate 生成的 DDL 语句,便于验证标签解析是否正确。