插件必须实现gorm.Plugin接口,否则db.Use()会panic;Register方法仅注册callback,不可执行建表等耗时操作;子session不继承插件,需显式重注册;设时间字段须用tx.Statement.SetColumn()。

插件必须实现 gorm.Plugin 接口,否则 db.Use() 直接 panic
GORM v2 的插件机制不是“随便注册个函数就行”,它强制要求类型实现 gorm.Plugin 接口——即带一个 Register(*gorm.DB) error 方法。如果你只写了 callback 函数、调了 db.Callback().Create().Register(...),但没包装成 Plugin 类型,db.Use(yourFunc) 会编译失败或运行时 panic:“plugin does not implement gorm.Plugin”。
常见错误:
- 把普通函数或闭包传给
db.Use()→ 报错:cannot use ... as gorm.Plugin value - 结构体实现了
Register,但方法接收者是值类型(func (t TimestampPlugin) Register(...))→ 实际可被调用,但若需在内部修改结构体字段则无效(不过插件通常不依赖实例状态) - 忘记
return nil→ GORM 认为注册失败,db.Use()立即 panic
Register 里只能注册 callback,不能做建表、连 Redis 等耗时操作
插件的 Register 方法在 db.Use() 时同步执行,属于 GORM 初始化关键路径。这里阻塞 = 整个应用启动卡住。
典型反模式:
立即学习“go语言免费学习笔记(深入)”;
- 在
Register中调用db.AutoMigrate()→ 启动依赖数据库可用,且每次 Use 都执行一遍(重复建表) - 初始化 Redis 客户端并
PING→ 启动失败风险陡增,且无法重试 - 读取远程配置中心(如 Nacos/Consul)→ 启动超时、网络抖动直接导致服务起不来
正确做法:把初始化逻辑拆到插件外部(比如 main() 中),插件只负责“声明行为”——告诉 GORM “当 Create 发生时,请执行这个函数”。
插件不自动继承到子 session,db.Session() 和 db.WithContext() 后失效
这是最隐蔽的坑:你在根 *gorm.DB 上调了 db.Use(&MyPlugin{}),但后续代码用了 db.Session(&gorm.Session{NewDB: true}) 或 db.WithContext(ctx),新生成的 DB 实例**默认不包含已注册插件**。
原因:GORM 插件注册信息存在 *gorm.DB 的私有字段中,而子 session 是全新实例,不会复制父级插件列表。
解决方案只有两个:
- 对每个需要插件能力的子 session 显式再调一次
session.Use(&MyPlugin{}) - 改用
db.Session(&gorm.Session{PrepareStmt: true})这类非 NewDB 模式(此时复用底层连接和插件)
注意:db.Debug().Where(...).Find() 这种链式调用产生的临时 session,同样不带插件——调试时钩子不触发,别急着怀疑插件写错了。
时间字段赋值必须用 tx.Statement.SetColumn(),不能直接改结构体字段
很多插件想自动设置 created_at 或 updated_at,但写成 u.CreatedAt = time.Now() 无效。因为 GORM 在生成 SQL 前,是从 tx.Statement.Schema 和 tx.Statement.Dest 提取字段值的,直接改 struct 字段不进 Statement 上下文。
正确写法只有这一种:
tx.Statement.SetColumn("created_at", time.Now())
其他要点:
- 字段名必须是 DB 名(
"created_at"),不是 Go 字段名("CreatedAt") - 确保该字段在 Schema 中存在且可写(没加
-或readonlytag) - 批量操作(如
db.CreateInBatches())中,SetColumn仍生效,但需注意作用域是否覆盖全部记录
跨 model 复用插件时,别硬编码字段名;应通过 tx.Statement.Schema.FieldsByDBName 动态查找,否则换张表就挂。


















