3、模型定义基础:结构体标签、表名映射、字段类型映射、主键与索引定义
GORM 的模型定义是数据库迁移的起点。一个结构体(struct)对应一张数据库表,结构体中的字段对应表中的列。通过结构体标签(Tag)和约定方法,开发者可以精确控制表名、字段类型、主键、索引等数据库元信息。本章将系统讲解模型定义的四大核心要素。
3.1 结构体标签:控制字段行为的声明式语法
GORM 使用 Go 结构体标签(Tag)来声明字段的数据库映射规则。标签格式为 gorm:"key1:value1;key2:value2",多个键值对之间用分号分隔。以下是常用标签键及其作用:
| 标签键 | 作用说明 | 示例值 |
|---|---|---|
column |
指定数据库列名,默认使用蛇形命名 | column:user_name |
type |
指定数据库字段类型,覆盖 GORM 自动推断 | type:varchar(100) |
size |
定义字段长度/大小,对 varchar/数字类型有效 | size:255 |
primaryKey |
标记为主键 | primaryKey |
autoIncrement |
自增属性 | autoIncrement |
index |
创建单列索引,可指定索引名 | index:idx_name |
uniqueIndex |
创建唯一索引 | uniqueIndex:idx_unique_email |
default |
设置默认值 | default:0 |
not null |
非空约束 | not null |
comment |
字段注释 | comment:用户昵称 |
unique |
唯一约束(旧版,推荐使用 uniqueIndex) | unique |
标签组合示例:一个典型的用户模型字段定义如下:
type User struct {
ID uint `gorm:"primaryKey;autoIncrement;column:id"`
Name string `gorm:"type:varchar(100);not null;default:'';comment:用户姓名"`
Email string `gorm:"uniqueIndex:idx_email;size:255;not null"`
Age int `gorm:"default:0;comment:年龄"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
}
3.2 表名映射:约定与自定义
GORM 默认将结构体名转换为蛇形复数形式作为表名。例如 User 对应 users,OrderItem 对应 order_items。当需要自定义表名时,有三种方式:
- 实现
TableName()接口:在结构体上定义方法,返回自定义表名。 - 使用
Table()方法临时指定:在查询链中调用db.Table("my_users")。 - 全局表名前缀:通过
gorm.Config的NamingStrategy设置前缀。
最佳实践:推荐在模型上实现 TableName() 方法,将表名逻辑与模型绑定,避免查询链中遗漏 Table() 调用。
func (User) TableName() string {
return "t_user" // 统一加前缀
}
3.3 字段类型映射:Go 类型 → 数据库类型
GORM 内置了 Go 基本类型到主流数据库类型的映射规则。理解映射关系有助于在迁移时避免类型不匹配问题。
| Go 类型 | MySQL 类型 | PostgreSQL 类型 | SQLite 类型 |
|---|---|---|---|
bool |
tinyint(1) |
boolean |
numeric |
int/uint |
bigint |
bigint |
integer |
int8/uint8 |
tinyint |
smallint |
integer |
int16/uint16 |
smallint |
smallint |
integer |
int32/uint32 |
int |
integer |
integer |
int64/uint64 |
bigint |
bigint |
integer |
float32 |
float |
real |
real |
float64 |
double |
double precision |
real |
string |
longtext |
text |
text |
time.Time |
datetime(3) |
timestamp with time zone |
datetime |
[]byte |
longblob |
bytea |
blob |
注意:当使用 string 类型且未指定 size 标签时,GORM 默认映射为 longtext(MySQL)或 text(PostgreSQL)。如果期望生成 varchar(255),必须显式添加 size:255 标签。
3.4 主键与索引定义
主键和索引是数据库性能与数据完整性的核心。GORM 提供了多种声明方式:
主键定义
- 约定主键:字段名为
ID或Id时,GORM 自动视为主键。 - 标签显式声明:使用
gorm:"primaryKey"标签。 - 复合主键:在多个字段上添加
primaryKey标签。
type Order struct {
OrderID uint `gorm:"primaryKey;autoIncrement"` // 单主键自增
UserID uint `gorm:"primaryKey"` // 复合主键的一部分
ProductID uint `gorm:"primaryKey"` // 复合主键的另一部分
}
索引定义
GORM 支持单列索引、复合索引和唯一索引,通过标签或结构体方法声明。
| 索引类型 | 标签方式 | 方法方式 |
|---|---|---|
| 单列索引 | gorm:"index" 或 gorm:"index:idx_name" |
— |
| 复合索引 | 多个字段使用相同索引名:gorm:"index:idx_user_product,sort:desc" |
func (User) TableIndexes() []gorm.IndexOption |
| 唯一索引 | gorm:"uniqueIndex" 或 gorm:"uniqueIndex:idx_unique_email" |
同上 |
| 索引排序 | gorm:"index:idx_name,sort:desc" |
在 IndexOption 中指定 Order |
复合索引示例:
type Order struct {
ID uint `gorm:"primaryKey"`
UserID uint `gorm:"index:idx_user_status;not null"`
Status string `gorm:"index:idx_user_status;size:20;default:pending"`
CreatedAt time.Time
}
// 等价于在迁移时创建复合索引:CREATE INDEX idx_user_status ON orders(user_id, status);
3.5 完整模型示例:综合运用
以下是一个综合了标签、表名、类型映射、主键和索引的完整模型定义,可直接用于迁移:
package model
import (
"time"
"gorm.io/gorm"
)
// Product 商品模型
type Product struct {
ID uint `gorm:"primaryKey;autoIncrement;column:id"`
Name string `gorm:"type:varchar(200);not null;default:'';comment:商品名称;index:idx_name"`
CategoryID uint `gorm:"not null;index:idx_category_price,sort:asc"`
Price float64 `gorm:"type:decimal(10,2);not null;default:0.00;index:idx_category_price,sort:desc"`
Stock int `gorm:"default:0;comment:库存数量"`
Description string `gorm:"type:text;comment:商品描述"`
IsActive bool `gorm:"default:true;comment:是否上架"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `gorm:"index"` // 软删除
}
// TableName 自定义表名
func (Product) TableName() string {
return "t_product"
}
// TableIndexes 声明额外索引
func (Product) TableIndexes() []gorm.IndexOption {
return []gorm.IndexOption{
{Name: "idx_category_status", Columns: []string{"category_id", "is_active"}, Unique: false},
}
}
迁移验证:使用 db.AutoMigrate(&Product{}) 执行迁移后,可通过 db.Migrator().HasTable("t_product") 和 db.Migrator().HasIndex(&Product{}, "idx_name") 验证表与索引是否创建成功。
3.6 常见陷阱与避坑指南
- 字段类型覆盖:当 Go 类型映射不符合需求时,务必使用
type标签显式指定,例如type:decimal(10,2)。 - 索引名冲突:不同模型间索引名不能重复,建议在索引名中包含表名缩写,如
idx_prod_name。 - 复合索引顺序:标签中
sort:asc或sort:desc控制索引列排序,影响查询性能。 - 软删除字段:
gorm.DeletedAt类型自动添加index标签,避免全表扫描。 - 表名缓存:
TableName()方法在迁移时只调用一次,运行时不会重复解析,性能无影响。