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。当需要自定义表名时,有三种方式:

  1. 实现 TableName() 接口:在结构体上定义方法,返回自定义表名。
  2. 使用 Table() 方法临时指定:在查询链中调用 db.Table("my_users")。
  3. 全局表名前缀:通过 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);
GORM 模型定义核心要素 模型结构体 结构体标签 表名映射 字段类型映射 主键与索引 column type/size TableName() NamingStrategy Go→DB 映射 自定义类型 primaryKey index/uniqueIndex 标签 + 约定方法 = 完整的数据库表定义 迁移时 GORM 自动解析生成 DDL

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() 方法在迁移时只调用一次,运行时不会重复解析,性能无影响。
从模型定义到数据库迁移的完整流程 定义结构体 添加标签 实现接口 AutoMigrate 字段 + 类型 约束 + 索引 表名 + 复合索引 CREATE TABLE 数据库表 + 索引 + 约束

3.7 小结