直接用gorm.model嵌入最省事,它预置id、createdat、updatedat、deletedat四字段并默认启用软删除;但列名不匹配需gorm:"column:xxx"、自定义主键需gorm:"primarykey"、忽略字段用gorm:"-",时间字段须注意类型与空值处理。

直接用 gorm.Model 嵌入是最省事的起点,但字段名、主键、时间戳行为不满足业务需求时,必须手动加 gorm 标签——别指望“默认”能覆盖所有场景。
用 gorm.Model 快速起步,但得知道它干了什么
它自带 ID、CreatedAt、UpdatedAt、DeletedAt 四个字段,类型和标签都已预设好,适合多数 CRUD 场景。嵌入后无需重复声明这四个字段,避免手误写错类型或漏掉 gorm:"primaryKey"。
DeletedAt 默认启用软删除,调用 Delete 时实际是更新该字段而非物理删除。如果表里没有 created_at 或字段名是 createtime,嵌入后仍需用标签覆盖:CreatedAt time.Time `gorm:"column:createtime"`。
- 字段名必须大写导出,小写字段(如
ignored string)完全不映射 - 嵌入后结构体字段顺序不影响映射,但
gorm.Model的字段会出现在最前面 - 如果你用 PostgreSQL,
DeletedAt类型是*time.Time,MySQL 同样适用;但若数据库列是bigint存 UNIX 时间戳,就不能直接嵌入
自定义主键和列名必须靠 gorm 标签
GORM 不会读取结构体字段注释或变量名大小写来推断数据库列名,一切以 gorm 标签为准。
- 主键不是
ID?必须显式加gorm:"primaryKey",比如UserID uint `gorm:"primaryKey"` - 列名和字段名不一致?必须用
gorm:"column:xxx",例如Username string `gorm:"column:username"` - 想跳过某个字段(如密码明文)不映射到数据库?加
gorm:"-",注意后面有空格,否则解析失败 - 字段名含下划线(如
user_name),GORM 默认转驼峰(UserName),但不会反向推导;必须用column显式指定
嵌入结构体时前缀控制容易被忽略
用 embedded 把子结构体字段“摊平”进父结构体很常见,但字段名冲突或需要区分来源时,embeddedPrefix 是关键。
- 不加前缀:
Address struct{ City string } `gorm:"embedded"`→ 生成列city - 加前缀:
Address struct{ City string } `gorm:"embedded;embeddedPrefix:addr_"`→ 生成列addr_city - 两个嵌入结构体含同名字段(如都含
Name),不加前缀会导致编译报错或覆盖,必须用前缀隔离 -
embeddedPrefix只影响列名,不影响 Go 结构体内存布局或访问方式
时间字段类型与零值行为要对齐数据库
time.Time 在 GORM 中默认映射为 DATETIME,但 MySQL 的 DATETIME 不支持纳秒精度,PostgreSQL 的 TIMESTAMP WITH TIME ZONE 则支持——类型不匹配会丢精度或报错。
- MySQL 用户建议用
CreatedAt time.Time `gorm:"type:datetime"`明确类型,避免驱动自动选timestamp导致时区问题 - 字段可为空?用
*time.Time而非time.Time,否则零值0001-01-01 00:00:00 +0000 UTC可能被数据库拒绝或截断 - 想存 UNIX 秒/毫秒/纳秒?把字段类型换成
int64并配autoCreateTime:milli等标签,不要混用time.Time和整数类型
最常被绕开又最容易出问题的,是时间字段的空值处理和数据库类型的显式声明——尤其当团队共用一套模型但部署在不同数据库时,time.Time 和 *time.Time 的差别会在某次上线后突然暴露。











