3. GORM 模型定义基础

GORM 通过 Go 结构体(struct)与数据库表建立映射关系。模型定义是 ORM 的核心起点,决定了字段类型、主键、表名、索引等数据库元信息。本章围绕结构体标签、表名映射、字段类型映射、主键与自增字段四个维度展开,确保模型定义准确、可维护。

3.1 结构体标签:gorm 标签语法

GORM 使用结构体字段上的 gorm 标签来声明数据库列属性。标签格式为键值对,多个属性用分号分隔。

标签语法规则:
  • 标签写在字段类型之后,反引号包裹:`gorm:"column:user_name;type:varchar(100);not null"`
  • 多个标签属性用 ; 分隔
  • 标签值中不要包含多余空格,否则可能导致解析异常
  • GORM 标签与 JSON/XML 标签可共存,互不干扰
标签属性 说明 示例
column 指定数据库列名 column:user_name
type 指定数据库字段类型 type:varchar(100)
size 字段长度/大小 size:255
primaryKey 声明为主键 primaryKey
autoIncrement 自增字段 autoIncrement
not null 非空约束 not null
unique 唯一约束 unique
default 默认值 default:0
comment 字段注释 comment:用户昵称
index 索引名,可组合索引 index:idx_name
uniqueIndex 唯一索引 uniqueIndex:uk_email
embedded 嵌入结构体 embedded
embeddedPrefix 嵌入字段前缀 embeddedPrefix:addr_
type User struct {
    ID        uint      `gorm:"column:id;primaryKey;autoIncrement"`
    Name      string    `gorm:"column:name;type:varchar(100);not null;comment:用户姓名"`
    Email     string    `gorm:"column:email;type:varchar(200);uniqueIndex:uk_email"`
    Age       int       `gorm:"column:age;type:int;default:0"`
    CreatedAt time.Time `gorm:"column:created_at;autoCreateTime"`
    UpdatedAt time.Time `gorm:"column:updated_at;autoUpdateTime"`
}
常见陷阱:
  • 标签中 column 与字段名不一致时,GORM 优先使用 column 指定的列名
  • autoIncrement 通常与 primaryKey 配合使用,单独使用可能在某些数据库(如 PostgreSQL)中不生效
  • size 对 int 类型无效,仅对 string 等变长类型生效

3.2 表名映射

GORM 默认将结构体名转换为蛇形复数形式作为表名。例如 UserInfo 映射为 user_infos。开发者可通过三种方式自定义表名。

方式 实现方法 优先级
实现 TableName() 接口 为结构体定义 func (User) TableName() string { return "t_user" } 最高
使用 Table() 方法动态指定 db.Table("t_user").Create(&user) 次高(仅作用于当前链式调用)
全局表名前缀/后缀 db.NamingStrategy = schema.NamingStrategy{TablePrefix: "t_"} 最低(影响所有模型)
// 方式一:实现 TableName 接口
type Product struct {
    ID   uint
    Name string
}

func (Product) TableName() string {
    return "t_product"
}

// 方式二:链式调用时临时指定
db.Table("t_product").Create(&product)

// 方式三:全局命名策略
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    NamingStrategy: schema.NamingStrategy{
        TablePrefix: "t_",      // 表名前缀
        SingularTable: false,   // 是否使用单数表名
    },
})
最佳实践:推荐在模型上实现 TableName() 方法,显式声明表名。这样表名与代码绑定,不受全局配置影响,便于代码审查和数据库迁移。
表名映射决策流程 结构体定义 TableName()? 是 返回自定义表名 否 全局策略? 是 前缀+蛇形复数 否 默认蛇形复数 优先级:TableName() > 链式 Table() > 全局命名策略 > 默认规则

3.3 字段类型映射

GORM 自动将 Go 数据类型映射为数据库字段类型。不同数据库的映射规则略有差异,下表以 MySQL 为例列出常见映射关系。

Go 类型 GORM 默认映射(MySQL) 推荐标签覆盖
bool tinyint(1) type:boolean
int, int8 ~ int64 bigint type:int 或 type:integer
uint, uint8 ~ uint64 bigint unsigned type:int unsigned
float32 float type:float(10,2)
float64 double type:double(10,2)
string longtext type:varchar(255) 或 size:255
time.Time datetime(3) type:timestamp 或 type:date
[]byte longblob type:blob 或 type:varbinary(255)
json.RawMessage json type:json
decimal.Decimal decimal(19,4) type:decimal(10,2)
类型映射注意事项:
  • string 类型若不指定 size 或 type,MySQL 下默认生成 longtext,性能较差,务必显式指定长度
  • time.Time 默认精度为毫秒(3位小数),若需秒级精度可加 type:datetime(0)
  • 枚举类型(ENUM)需通过 type:enum('a','b','c') 手动指定
  • GORM 支持自定义类型,需实现 Scanner 和 Valuer 接口
// 自定义类型映射示例
type Gender string

const (
    Male   Gender = "male"
    Female Gender = "female"
)

func (g *Gender) Scan(value interface{}) error {
    *g = Gender(value.([]byte))
    return nil
}

func (g Gender) Value() (driver.Value, error) {
    return string(g), nil
}

type Employee struct {
    ID     uint   `gorm:"primaryKey"`
    Name   string `gorm:"type:varchar(100)"`
    Gender Gender `gorm:"type:enum('male','female','other');default:'other'"`
}

3.4 主键与自增字段定义

主键是每条记录的唯一标识。GORM 支持单字段主键、复合主键,以及自增主键。定义方式灵活,可通过标签或约定实现。

主键类型 定义方式 示例
单字段主键(约定) 字段名为 ID 或 Id,类型为整型 ID uint
单字段主键(标签) 任意字段加 gorm:"primaryKey" UserID uint `gorm:"primaryKey"`
复合主键 多个字段分别加 primaryKey OrderID uint `gorm:"primaryKey"`
ItemID uint `gorm:"primaryKey"`
自增主键 primaryKey + autoIncrement ID uint `gorm:"primaryKey;autoIncrement"`
// 单字段自增主键(推荐方式)
type Order struct {
    ID        uint      `gorm:"primaryKey;autoIncrement;comment:订单ID"`
    OrderNo   string    `gorm:"type:varchar(32);uniqueIndex"`
    Amount    float64   `gorm:"type:decimal(10,2)"`
    CreatedAt time.Time `gorm:"autoCreateTime"`
}

// 复合主键示例
type OrderItem struct {
    OrderID   uint    `gorm:"primaryKey;comment:订单ID"`
    ItemID    uint    `gorm:"primaryKey;comment:商品ID"`
    Quantity  int     `gorm:"default:1"`
    Price     float64 `gorm:"type:decimal(10,2)"`
}

// 非自增主键(如业务主键)
type Country struct {
    Code string `gorm:"primaryKey;type:char(2);comment:国家代码"`
    Name string `gorm:"type:varchar(100)"`
}
自增主键注意事项:
  • 自增字段必须为整型(int/uint 系列),不支持字符串自增
  • 复合主键中只能有一个字段设置 autoIncrement,且通常为第一个主键字段
  • 使用 autoIncrement 时,GORM 在创建记录后会自动将数据库返回的自增 ID 回填到结构体字段
  • PostgreSQL 需使用 type:serial 或 type:bigserial 配合 autoIncrement
  • SQLite 自增主键需显式声明 type:integer primary key autoincrement
主键定义方式对比 约定方式:字段名 ID type User struct { ID uint // 自动主键 } 标签方式:primaryKey type User struct { UID uint `gorm:"primaryKey"` } 复合主键 type OrderItem struct { OrderID uint `gorm:"primaryKey"` ItemID uint `gorm:"primaryKey"` } 自增主键组合规则 单主键:primaryKey + autoIncrement → 自动生成自增序列 复合主键:仅第一个主键可加 autoIncrement,其余必须手动赋值

3.5 模型定义完整示例

综合以上知识点,给出一个生产级模型定义示例,涵盖主键、自增、字段类型、标签、表名映射、时间追踪等要素。

package model

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

// User 用户模型
// 表名:t_user
type User struct {
    ID        uint           `gorm:"primaryKey;autoIncrement;comment:用户ID"`
    Username  string         `gorm:"column:username;type:varchar(50);not null;uniqueIndex:uk_username;comment:用户名"`
    Email     string         `gorm:"column:email;type:varchar(200);not null;uniqueIndex:uk_email;comment:邮箱"`
    Password  string         `gorm:"column:password;type:varchar(255);not null;comment:密码"`
    Nickname  string         `gorm:"column:nickname;type:varchar(100);default:'';comment:昵称"`
    Age       int            `gorm:"column:age;type:int;default:0;comment:年龄"`
    Status    int            `gorm:"column:status;type:tinyint;default:1;comment:状态 1-正常 0-禁用"`
    CreatedAt time.Time      `gorm:"column:created_at;autoCreateTime;comment:创建时间"`
    UpdatedAt time.Time      `gorm:"column:updated_at;autoUpdateTime;comment:更新时间"`
    DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;index;comment:删除时间"`
}

func (User) TableName() string {
    return "t_user"
}
模型定义检查清单:
  • ✅ 主键字段是否明确声明 primaryKey?
  • ✅ 自增字段是否同时包含 primaryKey 和 autoIncrement?
  • ✅ 字符串字段是否指定了 type:varchar(N) 或 size?
  • ✅ 是否实现了 TableName() 方法显式声明表名?
  • ✅ 时间字段是否使用了 autoCreateTime / autoUpdateTime?
  • ✅ 软删除字段是否使用了 gorm.DeletedAt 类型?
  • ✅ 唯一索引、普通索引是否按需添加?
  • ✅ 字段注释是否完整,便于 DBA 审核?