模型定义
GORM 通过 Go 结构体定义数据库模型,利用 Struct Tag 控制字段映射、约束和关联关系。
gorm.Model — 内嵌基础模型
GORM 提供了一个预定义的模型结构:
// gorm.Model 的定义
type Model struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}| 字段 | 类型 | 说明 |
|---|---|---|
ID |
uint |
自增主键(MySQL)/ 序列主键(PostgreSQL) |
CreatedAt |
time.Time |
创建时间,创建时自动设置 |
UpdatedAt |
time.Time |
更新时间,更新时自动设置 |
DeletedAt |
gorm.DeletedAt |
软删除标记(NULL 表示未删除) |
// 嵌入 gorm.Model
type User struct {
gorm.Model
Name string
Age int
}
// 等价于:
type User struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
Name string
Age int
}💡 建议统一嵌入
gorm.Model,省去重复定义。若不需要软删除,可手动定义字段而不嵌入。
约定大于配置
GORM 遵循一系列命名约定,理解这些约定可以免除大量配置:
| 约定 | 规则 | 示例 |
|---|---|---|
| 表名 | 结构体名转蛇形复数 | User → users、UserProfile → user_profiles |
| 列名 | 字段名转蛇形 | UserName → user_name、ID → id |
| 主键 | 名称为 ID 的 uint 字段 |
ID uint 自动为主键 |
| 创建时间 | 名称为 CreatedAt 的 time.Time 字段 |
创建时自动赋值 |
| 更新时间 | 名称为 UpdatedAt 的 time.Time 字段 |
更新时自动赋值 |
| 软删除 | 名称为 DeletedAt 的 gorm.DeletedAt 字段 |
删除时设置时间,查询时自动过滤 |
修改命名策略
import "gorm.io/gorm/schema"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: "t_", // 表名前缀 → "t_users"
SingularTable: true, // 单数表名 → "user" 而非 "users"
NoLowerCase: true, // 保持大写 → "User"
NameReplacer: strings.NewReplacer("CID", "Cid"), // 自定义替换
},
})字段标签(Struct Tags)完整参考
字段标签写在结构体字段的 gorm tag 中,使用分号 ; 分隔多个属性:
type User struct {
ID uint `gorm:"primaryKey;autoIncrement;comment:用户ID"`
Email string `gorm:"type:varchar(100);uniqueIndex;not null;comment:邮箱"`
Name string `gorm:"size:100;default:'';comment:昵称"`
Age int `gorm:"default:0;check:age >= 0;comment:年龄"`
Balance float64 `gorm:"type:decimal(10,2);default:0.00"`
IsActive bool `gorm:"default:true"`
Metadata string `gorm:"type:json;serializer:json"`
CreatedAt int64 `gorm:"autoCreateTime:milli"` // 毫秒时间戳
UpdatedAt int64 `gorm:"autoUpdateTime:milli"`
}字段级别标签
| 标签 | 说明 | 示例 |
|---|---|---|
column:name |
指定列名 | column:user_name |
type:sqlType |
指定 SQL 类型 | type:varchar(255)、type:text、type:decimal(10,2) |
size:n |
列大小 | size:100(相当于 varchar(100)) |
primaryKey |
声明为主键 | — |
autoIncrement |
自增(MySQL) | — |
unique |
唯一约束 | — |
uniqueIndex |
唯一索引 | — |
index |
普通索引 | — |
not null |
非空约束 | — |
default:val |
默认值 | default:''、default:0、default:CURRENT_TIMESTAMP |
comment:text |
字段注释 | comment:用户手机号 |
check:expr |
CHECK 约束 | check:age >= 0 |
<- |
只读(不写入) | <-:false(从不写入);<-:create(仅创建时写);<-:update(仅更新时写) |
-> |
只写(不读取) | ->:false(从不读取) |
- |
忽略字段 | 不映射到数据库 |
autoCreateTime |
创建时自动设置时间 | autoCreateTime(秒), autoCreateTime:nano, autoCreateTime:milli |
autoUpdateTime |
更新时自动设置时间 | autoUpdateTime(秒), autoUpdateTime:nano, autoUpdateTime:milli |
embedded |
嵌入结构体 | — |
embeddedPrefix:xx |
嵌入结构体列名前缀 | embeddedPrefix:addr_ |
serializer:json |
JSON 序列化器 | serializer:json、serializer:gob、serializer:unixtime |
关联标签
| 标签 | 说明 |
|---|---|
foreignKey:Field |
指定外键字段 |
references:Field |
指定被引用的字段 |
polymorphic:Field |
多态关联的类型字段名 |
polymorphicValue:val |
多态关联的类型值 |
many2many:table |
多对多中间表名 |
joinForeignKey:Field |
多对多关联外键 |
joinReferences:Field |
多对多引用 |
constraint:OnUpdate:xxx,OnDelete:xxx |
外键约束行为 |
💡 标签大小写不敏感,
PRIMARYKEY等同于primaryKey。值需要区分大小写的地方使用 correct case。
字段读写权限控制
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"<-"` // 允许读写(默认)
Password string `gorm:"<-:false"` // 禁止写入(从 DB 读,不写回)
Secret string `gorm:"->:false"` // 禁止读取(写入 DB,不读出)
Internal string `gorm:"-"` // 完全忽略
Token string `gorm:"<-:create"` // 仅创建时写入
UpdatedBy string `gorm:"<-:update"` // 仅更新时写入
}// <-:false 的典型使用场景:密码哈希,只从数据库读取但通过其他方式写入
// 更新时 Password 字段不会被 Updates 覆盖
db.Model(&user).Updates(User{Name: "新名字", Password: "xxx"}) // Password 忽略🚨 陷阱:
<-:false在Create时也会被忽略。如果创建时需要写入而更新时不需要,使用<-:create。
时间字段类型详解
使用 time.Time
type User struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time // 秒级精度,创建时自动设置
UpdatedAt time.Time // 秒级精度,更新时自动设置
DeletedAt gorm.DeletedAt `gorm:"index"` // 软删除
}使用时间戳(int64)
type User struct {
ID uint `gorm:"primaryKey"`
CreatedAt int64 `gorm:"autoCreateTime:milli"` // 毫秒时间戳
UpdatedAt int64 `gorm:"autoUpdateTime:milli"` // 毫秒时间戳
}自定义时间格式
type User struct {
Birthday *time.Time `gorm:"type:date"` // 日期
LoginAt time.Time `gorm:"type:datetime(3)"` // 毫秒精度
Extra time.Time `gorm:"type:timestamp"` // 时间戳
}| Go 类型 | MySQL 默认映射 | PostgreSQL 默认映射 |
|---|---|---|
time.Time |
datetime(3) |
timestamptz |
time.Time + type:date |
date |
date |
time.Time + type:time |
time |
time |
*time.Time |
datetime(3) NULL |
timestamptz NULL |
int64 + autoCreateTime |
bigint |
bigint |
gorm.DeletedAt |
datetime(3) NULL |
timestamptz NULL |
🚨 陷阱:PostgreSQL 中
time.Time默认映射到timestamptz(带时区)。如果需要不带时区的时间戳,使用type:timestamp。
嵌入结构体
// 嵌入结构体(embedded: 展开字段)
type Audit struct {
CreatedBy string `gorm:"size:100;comment:创建人"`
UpdatedBy string `gorm:"size:100;comment:更新人"`
}
type User struct {
gorm.Model
Name string
Audit // 字段展开到 users 表
}
// 生成列:id, created_at, updated_at, deleted_at, name, created_by, updated_by
type User2 struct {
gorm.Model
Name string
Audit Audit `gorm:"embedded;embeddedPrefix:audit_"` // 带前缀
}
// 生成列:..., audit_created_by, audit_updated_by
// 指针嵌入
type User3 struct {
gorm.Model
Name string
Audit *Audit `gorm:"embedded"` // NULL 可选的嵌入
}类型映射对照表
MySQL
| Go 类型 | MySQL 类型 |
|---|---|
bool |
tinyint(1) |
int, uint |
bigint |
int8 |
tinyint |
int16 |
smallint |
int32 |
int |
int64 |
bigint |
uint8 |
tinyint unsigned |
uint16 |
smallint unsigned |
uint32 |
int unsigned |
uint64 |
bigint unsigned |
float32 |
float |
float64 |
double |
string(无 size) |
longtext |
string(size:255) |
varchar(255) |
[]byte |
longblob |
time.Time |
datetime(3) |
*time.Time |
datetime(3) NULL |
gorm.DeletedAt |
datetime(3) NULL |
PostgreSQL
| Go 类型 | PostgreSQL 类型 |
|---|---|
bool |
boolean |
int, uint |
bigint |
int16 |
smallint |
int32 |
integer |
int64 |
bigint |
float32 |
real |
float64 |
double precision |
string |
text |
time.Time |
timestamptz |
*time.Time |
timestamptz NULL |
[]byte |
bytea |
gorm.DeletedAt |
timestamptz NULL |
💡 使用
type标签可覆盖默认映射。比如string字段想存为text而非varchar(255),使用gorm:"type:text"。
默认值处理
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"default:未命名"`
Age int `gorm:"default:18"`
IsVIP bool `gorm:"default:false"` // 注意:false 是零值,需特殊处理
Status string `gorm:"default:active"`
Bio string `gorm:"default:NULL"` // NULL 默认值
}🔬 深入原理:Go 零值(0, "", false)与数据库默认值的交互:
// 结构体零值字段不会被写入 INSERT(让数据库默认值生效)
db.Create(&User{Name: "张三"})
// INSERT INTO users (name) VALUES ('张三')
// Age 用数据库默认值 18,IsVIP 用 false
// 要显式写入零值,使用指针类型或 Select
db.Select("Age", "IsVIP").Create(&User{Name: "张三"})
// INSERT INTO users (name, age, is_vip) VALUES ('张三', 0, false)// 推荐:使用指针表示"可能为零值"的字段
type User struct {
Age *int `gorm:"default:18"` // nil → 用默认值,非 nil → 写入具体值
IsVIP *bool `gorm:"default:false"`
}
age := 0
db.Create(&User{Name: "张三", Age: &age}) // 显式写入 Age=0
db.Create(&User{Name: "李四"}) // Age 用数据库默认值💡 最佳实践:对于有默认值的数值/布尔字段,使用指针类型
*int/*bool,这样nil表示"使用默认值",非nil表示"显式设值"(包括零值)。
自定义表名
// 方式一:实现 Tabler 接口(优先级最高)
func (User) TableName() string {
return "sys_users"
}
// 方式二:临时指定表名
db.Table("sys_users").Find(&users)
// 方式三:通过 NamingStrategy 全局设置(见上文)💡
TableName()方法优先级最高,适合固定命名规则(如带模块前缀的表名)。
列名自定义
type User struct {
ID uint `gorm:"primaryKey;column:user_id"` // 自定义列名
UserName string `gorm:"column:username"` // 自定义列名
PassWord string `gorm:"column:pass_word"` // 与蛇形命名一致,可省略
}常见陷阱汇总
| 陷阱 | 说明 |
|---|---|
| 🚨 表名复数 | 默认 User → users,若数据库中为 user,需设置 SingularTable: true |
| 🚨 零值不更新 | Updates(struct) 忽略零值字段,用 Updates(map) 或 Select |
| 🚨 大 string 字段 | 未指定 size 的 string → MySQL 中为 longtext,应显式 size:255 |
| 🚨 CreatedAt 精度 | 默认 datetime(3)(毫秒),若数据库列是 datetime(秒),会报类型不匹配错误 |
| 🚨 保留字冲突 | 列名如 order、group 是 SQL 保留字,应用 column:order 并加引号引用 |
| 🚨 外键约束 | GORM 默认在迁移时创建物理外键,生产环境大多禁用 |