GORM 的 default 标签仅在数据库层面生效,不解析 Go 表达式或函数,仅支持字面量;自定义类型字段无法通过该标签设默认值,应改用 BeforeCreate 钩子或结构体初始化。

GORM 的 default 标签只在数据库层面生效,不执行 Go 表达式,也不能调用函数(如 time.Now() 或 time.Parse())——这是绝大多数人踩坑的根源。
gorm:"default:xxx" 只接受字面量值
数据库建表时,GORM 把 default 后的内容原样写进 SQL 的 DEFAULT 子句。它不做任何 Go 层解析:
-
Status Status `gorm:"default:0"`→ 生成DEFAULT 0,可行,因为0是整数字面量 -
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP"`→ 生成DEFAULT CURRENT_TIMESTAMP,可行,因为这是 MySQL 内置函数 -
StartTime *LocalTime `gorm:"default:time.Parse('2006-01-02', '2999-12-31')"`→ ❌ 失败,数据库不认识 Go 函数,建表直接报错或默认值被忽略 -
Name string `gorm:"default:hello world"`→ ✅ 但注意:MySQL 要求字符串加单引号,应写成default:'hello world'
自定义类型字段无法用 gorm:"default" 设默认值
像 *LocalTime 这类实现了 Valuer/Scanner 的指针类型,GORM 不会把 default 值转成该类型的实例。它只负责把字面量塞进 SQL,而数据库不知道怎么把 '2999-12-31 23:59:59' 映射成你的 *LocalTime 实例。
- 即使你写了
gorm:"default:'2999-12-31 23:59:59'",插入时数据库存的是字符串或时间类型,Scan()方法不会被触发来构造*LocalTime - 更危险的是:若字段是
*LocalTime(指针),default设为NULL才安全;设成非空字面量反而可能因类型不匹配被静默丢弃 - 正确做法是放弃
default标签,改用 Go 层初始化(例如在 struct 初始化或BeforeCreate钩子中赋值)
替代方案:用 GORM 钩子或结构体初始化设默认值
对需要运行时逻辑(如当前时间、枚举常量、格式化解析)的默认值,必须移到 Go 层处理:
- 用
BeforeCreate钩子填充指针字段:func (t *Task) BeforeCreate(tx *gorm.DB) error { if t.StartTime == nil { now := LocalTime(time.Now()) t.StartTime = &now } return nil } - 结构体初始化时预设:
t := Task{ Status: TaskStatusDisabled, StartTime: &LocalTime{time.Date(2999, 12, 31, 23, 59, 59, 0, time.Local)}, } - 对于
time.Time类型字段,可直接用gorm:"default:CURRENT_TIMESTAMP"(MySQL)或gorm:"default:now()"(PostgreSQL),但注意这和 Go 层的CreatedAt行为不同:它由数据库生成,不经过Valuer,也不受 Go 时区影响
最易被忽略的一点:default 是纯数据库行为,和 Go 的零值、结构体字段初始化、GORM 钩子完全无关。一旦你写了 default,就等于把默认逻辑交给了数据库引擎——它既不会调你的 Value() 方法,也不会理解你的自定义类型语义。











