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() 方法,显式声明表名。这样表名与代码绑定,不受全局配置影响,便于代码审查和数据库迁移。
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
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 审核?