GORM v2钩子需严格匹配:接收者必须为struct,参数为gorm.DB,同包定义且大小写精确;Save()不触发BeforeCreate/BeforeUpdate而用BeforeSave;软删除走AfterUpdate非AfterDelete;AfterFind早于Preload故无法访问关联数据。

BeforeCreate/BeforeUpdate 钩子不执行?先查接收者和签名
钩子写了但没触发,不是 GORM 失效,而是它根本没认出来。GORM v2 只识别严格匹配的指针方法:func (u *User) BeforeCreate(tx *gorm.DB) error。
- 接收者必须是
*User,写成func (u User)→ 静默失效(改的是副本) - 参数名可以是
tx或db,但类型必须是*gorm.DB,少*或类型写成gorm.DB→ 不调用 - 方法必须定义在
Userstruct 所在的同一个包里,哪怕只差一个import或放在hooks/子目录 → GORM 当不存在 - 大小写敏感:
Beforecreate或beforeCreate→ 完全无效
Save() 为什么绕过 BeforeCreate 和 BeforeUpdate?
Save() 是 GORM 的“兜底更新”,它不区分新增还是修改,也不走 BeforeCreate 或 BeforeUpdate,只触发 BeforeSave 和 AfterSave —— 这是绝大多数时间字段不更新的根源。
- 想触发
BeforeCreate:用db.Create(&u) - 想触发
BeforeUpdate:用db.Model(&u).Update("status", "done")或db.Updates(&u) - 如果模型字段是
time.Time(非指针),直接赋值u.UpdatedAt = time.Now()在钩子里无效 —— 必须用tx.Statement.SetColumn("updated_at", time.Now()),否则生成 SQL 时仍用零值
软删除 Delete() 触发的是 AfterUpdate,不是 AfterDelete
调用 db.Delete(&u) 默认走软删除流程,GORM 实际执行的是 UPDATE,所以钩子顺序是:BeforeDelete → AfterUpdate,AfterDelete 永远不会被调用。
- 需要在软删除时更新审计字段(如
DeletedAt、UpdatedBy),逻辑必须写在BeforeDelete里 - 若字段类型是
time.Time,u.DeletedAt = time.Now()无效;应改用tx.Statement.SetColumn("deleted_at", time.Now()) - 真要物理删除(跳过软删),得显式加
db.Unscoped().Delete(&u),此时才走BeforeDelete→AfterDelete
AfterFind 读不到 Preload 关联数据?时机太早
AfterFind 在单条记录从数据库取出后**立即执行**,此时 Preload 或 Joins 的关联数据还没加载,访问 u.Profile 基本是 nil 或零值。
立即学习“go语言免费学习笔记(深入)”;
- 不要在
AfterFind里依赖关联字段,比如u.Profile.Name或len(u.Orders) - 批量查询
db.Preload("Profile").Find(&users)时,AfterFind会为每个user单独触发一次,不是只调一次 - 真正需要处理关联数据,应在业务层查完再统一处理,或封装查询函数,在
Preload后手动遍历


















