3. 模型定义基础:结构体标签、表名映射与字段类型

在 GORM 中,模型(Model)是 Go 结构体与数据库表之间的桥梁。通过结构体标签(Tag)和约定方法,开发者可以精确控制表名、字段类型、主键、自增 ID 以及时间字段的自动管理。本章将系统讲解这些核心映射规则,帮助你写出清晰、可维护的模型定义。

核心原则: GORM 遵循“约定优于配置”的设计哲学。只要遵循默认命名规则,大部分映射可以自动完成;当需要偏离默认行为时,通过标签或接口方法显式声明。

3.1 结构体标签(gorm 标签)

GORM 使用 gorm 标签来定义字段级别的映射规则。标签格式为 gorm:"key1:value1;key2:value2",多个键值对之间用分号分隔。

标签键 说明 示例值
column 指定数据库列名 column:user_name
type 指定数据库字段类型 type:varchar(100)
size 字段大小/长度(varchar 等类型) size:255
primaryKey 标记为主键 primaryKey
autoIncrement 启用自增 autoIncrement
not null 非空约束 not null
unique 唯一约束 unique
default 默认值 default:0
comment 字段注释 comment:用户昵称
index 索引(可指定索引名) index:idx_name
embedded 嵌入结构体 embedded
- 忽略该字段(不映射到数据库) -
最佳实践: 使用 column 标签显式指定列名可以避免因 Go 字段名重构导致数据库列名意外变化。对于生产环境模型,建议始终为关键字段添加 column 标签。

3.2 表名映射

GORM 默认将结构体名转换为蛇形复数形式作为表名。例如 UserInfo 对应 user_infos。当需要自定义表名时,有三种方式:

  1. 实现 Tabler 接口: 定义 TableName() string 方法,返回自定义表名。
  2. 使用 Table() 方法: 在查询时动态指定表名。
  3. 全局表名前缀: 通过 gorm.Config 的 NamingStrategy 设置前缀。
// 方式一:实现 Tabler 接口
type User struct {
  ID   uint
  Name string
}

func (User) TableName() string {
  return "t_user"  // 自定义表名
}

// 方式二:动态指定表名
db.Table("t_user").Where("name = ?", "张三").Find(&users)

// 方式三:全局前缀
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
  NamingStrategy: schema.NamingStrategy{
    TablePrefix: "t_",  // 所有表名自动添加 t_ 前缀
  },
})
注意: 如果同时使用 TableName() 方法和 Table() 方法,Table() 的优先级更高。在同一个项目中,建议统一使用一种方式管理表名,避免混淆。

3.3 字段类型映射

GORM 自动将 Go 数据类型映射到数据库字段类型。下表列出常见映射关系:

Go 类型 MySQL 类型 PostgreSQL 类型 SQLite 类型
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
bool tinyint(1) boolean numeric
time.Time datetime(3) timestamp with time zone datetime
[]byte longblob bytea blob

当需要精确控制字段类型时,使用 type 标签覆盖默认映射:

type Product struct {
  ID          uint   `gorm:"primaryKey;autoIncrement"`
  Name        string `gorm:"type:varchar(200);not null;comment:商品名称"`
  Price       float64 `gorm:"type:decimal(10,2);default:0.00"`
  Description string `gorm:"type:text"`
  Status      int8   `gorm:"type:tinyint(1);default:1;comment:状态 1-上架 0-下架"`
}

3.4 主键与自增 ID

GORM 默认将名为 ID 的字段作为主键,并自动启用自增。如果字段名不是 ID,需要显式使用 primaryKey 标签。

// 默认主键(自动识别)
type User struct {
  ID   uint   // 自动成为主键并自增
  Name string
}

// 自定义主键字段名
type Order struct {
  OrderID uint   `gorm:"primaryKey;autoIncrement;column:order_id"`
  Amount  float64
}

// 复合主键
type OrderItem struct {
  OrderID   uint `gorm:"primaryKey"`
  ProductID uint `gorm:"primaryKey"`
  Quantity  int
}
自增 ID 注意事项:
  • 自增字段类型必须是整数类型(uint、int、uint64 等)。
  • 使用 autoIncrement 标签显式声明自增,避免依赖数据库默认行为。
  • 复合主键不支持自增,每个主键字段需要手动赋值。
  • 在分布式场景下,建议使用 UUID 或雪花算法 ID 替代自增主键。

3.5 时间字段自动管理

GORM 通过约定字段名自动管理创建时间和更新时间。当结构体包含 CreatedAt 和 UpdatedAt 字段时,GORM 会在插入和更新时自动填充当前时间。

type User struct {
  ID        uint      `gorm:"primaryKey;autoIncrement"`
  Name      string    `gorm:"type:varchar(100);not null"`
  CreatedAt time.Time `gorm:"autoCreateTime"`  // 创建时自动填充
  UpdatedAt time.Time `gorm:"autoUpdateTime"`  // 更新时自动填充
  DeletedAt gorm.DeletedAt `gorm:"index"`      // 软删除字段
}

时间字段的自动管理支持三种模式:

标签 行为 适用场景
autoCreateTime 创建记录时自动设置当前时间,后续不再修改 记录创建时间
autoUpdateTime 创建和更新记录时自动设置为当前时间 记录最后修改时间
autoCreateTime:milli 创建时自动填充毫秒级时间戳 需要高精度时间戳的场景
autoUpdateTime:nano 更新时自动填充纳秒级时间戳 需要极高精度的时间记录
时间精度选择: 默认使用 time.Time 类型时,数据库存储精度为秒级。如果业务需要毫秒或纳秒精度,使用 autoCreateTime:milli 或 autoUpdateTime:nano 标签,并将字段类型改为 uint 或 int64。
GORM 模型定义核心映射流程 Go 结构体 type User struct { ... } gorm 标签解析 column / type / size 数据库表 CREATE TABLE ... 表名映射 TableName() / Table() 字段类型映射 Go 类型 → DB 类型 主键与自增 primaryKey / autoIncrement 时间字段自动管理 CreatedAt / UpdatedAt / DeletedAt autoCreateTime 插入时自动填充 autoUpdateTime 更新时自动填充 DeletedAt 软删除标记 提示:通过 gorm 标签和接口方法,Go 结构体可以精确映射到数据库表结构

3.6 完整模型示例

综合以上知识点,下面是一个生产级别的模型定义示例:

package model

import (
  "time"
  "gorm.io/gorm"
)

// User 用户模型
type User struct {
  ID        uint           `gorm:"primaryKey;autoIncrement;column:id"`
  Username  string         `gorm:"type:varchar(50);not null;unique;index:idx_username;comment:用户名"`
  Email     string         `gorm:"type:varchar(100);not null;unique;comment:邮箱"`
  Age       int8           `gorm:"type:tinyint(3);default:0;comment:年龄"`
  Avatar    string         `gorm:"type:varchar(255);default:'';comment:头像URL"`
  Status    int8           `gorm:"type:tinyint(1);default:1;comment:状态 1-正常 0-禁用"`
  CreatedAt time.Time      `gorm:"autoCreateTime;comment:创建时间"`
  UpdatedAt time.Time      `gorm:"autoUpdateTime;comment:更新时间"`
  DeletedAt gorm.DeletedAt `gorm:"index;comment:删除时间"`
}

// TableName 自定义表名
func (User) TableName() string {
  return "t_user"
}
模型设计建议:
  • 为每个字段添加 comment 标签,便于数据库维护和文档生成。
  • 为查询频繁的字段添加 index 标签,提升查询性能。
  • 使用 gorm.DeletedAt 实现软删除,避免物理删除数据。
  • 时间字段统一使用 time.Time 类型,保持代码可读性。

3.7 常见错误与避坑指南

错误场景 错误原因 解决方案
字段未映射到数据库 字段名首字母小写(未导出) 确保字段名首字母大写
自增 ID 不生效 字段类型不是整数或未加 autoIncrement 标签 使用 uint 类型并添加 autoIncrement 标签
时间字段未自动填充 字段名不是 CreatedAt/UpdatedAt 或缺少标签 使用约定字段名或添加 autoCreateTime/autoUpdateTime 标签
表名与预期不符 未实现 TableName() 方法且默认复数规则不符合需求 实现 Tabler 接口或配置 NamingStrategy
字段类型不匹配 Go 类型与数据库类型不兼容 使用 type 标签显式指定数据库类型
重要提醒: 模型定义是数据库迁移的基础。在定义模型时,务必考虑字段的约束(非空、唯一、默认值)和索引策略,这些将直接影响数据库的性能和数据完整性。建议在模型定义阶段就与 DBA 或后端架构师对齐规范。