revel 整合 gorm 需手动管理 *gorm.db 生命周期并注入请求上下文,初始化在 init() 中配置连接池,分库独立变量,模型方法显式传参,事务由 controller 显式控制,避免并发写 params。

Revel 框架本身不内置 ORM,整合 GORM 做增删改查必须手动管理 *gorm.DB 实例的生命周期,并确保它在请求上下文中可访问——不能直接用全局变量裸奔调用,否则并发下会 panic 或连接错乱。
初始化 GORM 连接池要放在 app/init.go 的 init() 里
Revel 的启动流程中,init() 是最早可执行逻辑的位置,适合做 DB 初始化。GORM v2 自带连接池,但需显式配置,否则默认参数(如 MaxOpenConns=0)可能触发系统级限制或连接耗尽。
- 用
gorm.Open()创建实例后,立刻调用db.DB().SetMaxOpenConns()和db.DB().SetMaxIdleConns() - 不要复用同一个
*gorm.DB实例处理多租户或分库场景;不同库(如主从、分片)必须初始化为独立变量,例如DBMaster、DBShard1 - 连接字符串中的
parseTime=True和loc=Local必须加上,否则time.Time字段解析失败,报invalid time format
模型层不能直接 import 全局 DB 变量
把 DBMaster 这类变量直接 import 到 model 文件里调用,看似简单,实则破坏请求隔离:中间件、filter、controller 修改了事务状态,后续 model 调用可能误用已 commit/rollback 的连接。
- 正确做法是通过 Revel 的
revel.InterceptFunc注入 DB 实例到revel.Controller的Params或自定义字段中 - 在 filter 中根据路由、Header 或 session 决定用哪个库,再绑定到
c.Params.Set("db", dbInstance) - model 方法接收
*gorm.DB作为参数,而不是硬编码引用全局变量,例如:func GetUser(db *gorm.DB, id uint) (*User, error)
CRUD 操作必须显式传入 *gorm.DB 实例
GORM 的链式调用(如 db.Where(...).First())返回的是新拷贝的 *gorm.DB,它携带当前查询上下文。如果没传对实例,操作会静默失败或打到错误库上。
-
Create()后检查result.Error和result.RowsAffected,别只看err != nil—— GORM v2 的error可能为nil但实际没写入(比如违反唯一约束但未开启AllowGlobalUpdate) - 软删除依赖
DeletedAt字段,但 Revel 的 controller 层不会自动识别该字段;需在 model 定义中显式加 tag:DeletedAt gorm.DeletedAt `gorm:"index"` - 批量插入慎用
CreateInBatches(),它内部会拆成多个事务;高并发下若没控制 batch size,容易触发 MySQL 的max_allowed_packet或锁表
事务必须由 controller 或 service 层显式控制
GORM 的 Transaction() 是函数式 API,无法跨函数自动传播。Revel 的 filter 不适合开事务,因为 filter 执行时机早于 controller,且无法保证 rollback 能覆盖所有分支路径。
- 事务起点应在 controller 的 action 方法内,用
db.Transaction(func(tx *gorm.DB) error { ... }) - 不要在 model 函数里调用
tx.Create()后直接 return,必须让 error 透出到外层事务函数,否则事务不会 rollback - 涉及多个库的事务(如跨分片写入)无法靠 GORM 原生事务保证一致性,得退回到最终一致性方案,比如发消息 + 补偿任务
最容易被忽略的一点:Revel 的 Controller 是每次请求 new 出来的,但它的 Params 是 map 类型,不是线程安全的。如果在并发 filter 里往 c.Params 写 DB 实例,必须加锁或改用 c.Request.Context 存储,否则会出现 A 请求拿到 B 请求的 DB 连接。











