Revel 集成 GORM 需解决连接初始化、请求级 DB 安全传递、模型层获取对应 *gorm.DB 三大问题:在 app/init.go 初始化多个线程安全的 DB 实例,用 filter 绑定分片 DB 到 Controller,模型层通过参数或 receiver 获取运行时 DB 实例。

Revel 框架本身不内置 ORM,集成 GORM 不是“装个包就能用”,而是要解决三个实际问题:连接怎么初始化、请求生命周期内 DB 实例怎么安全传递、模型层怎么拿到当前请求对应的 *gorm.DB。直接全局赋值 gorm.DB 或在 handler 里反复 Open 都会出问题。
app/init.go 里初始化多个 DB 实例,别用 global 变量裸传
Revel 的 init() 是启动入口,适合做连接池预热。但注意:GORM v2 的 *gorm.DB 是线程安全的,可复用,但不能被多个 goroutine 同时修改配置(比如调用 Session() 改 Context 时)。所以你应该:
- 声明多个顶层变量,如
DBMaster、DBShard1,类型为*gorm.DB - 每个实例用独立 DSN 初始化,显式传入
gorm.Config,避免共享底层sql.DB连接池 - 禁用
AutoMigrate在 init 阶段执行——它不是幂等的,上线后改表结构容易误触发 - 用
revel.InterceptFunc(dbFilter, revel.BEFORE)注册 filter,而不是在每个 controller 里手动取 DB
用 Revel filter 绑定 DB 到 revel.Controller,别在 action 里硬编码
Revel 的 Controller 是 per-request 实例,天然适合承载请求级上下文。你在 filter 里做的不是“切换全局 DB”,而是把选好的 *gorm.DB 实例塞进 c.Params 或自定义字段:
- 定义一个接口或字段,比如
c.TX *gorm.DB(推荐加TX前缀,暗示它是事务就绪的实例) - filter 中根据 URL path、header、cookie 或 token 解析分片键(如
user_id % 2),再查表或映射决定用DBShard1还是DBShard2 - 不要在 filter 里调用
Begin()——事务应在 service 层按需开启,filter 只负责提供“干净的、已配置好日志和 trace 的连接” - 如果用了 GORM 的
WithContext(c.Request.Context()),确保 context 没被 cancel,否则后续查询会卡住
模型层统一从 Controller 获取 DB,别写死 DBMaster
你的 User、Order 等 struct 不该直接引用全局 DBMaster,而应通过参数或 receiver 获取运行时绑定的实例:
- 在 model 方法中加
db *gorm.DB参数,例如func (u *User) Save(db *gorm.DB) error - 或让 model 接收
*revel.Controller,从中取c.TX—— 这样能自然继承 request-scoped context 和 logger - GORM 的
Preload和Joins默认走同一个*gorm.DB实例,所以只要 root 查询用的是分片 DB,关联表也会自动路由过去(前提是关联字段也落在同一分片) - 避免在 model 里调用
db.Debug()上线环境——它会把完整 SQL 打到日志,可能泄露敏感参数
事务必须显式控制,GORM 的 Transaction 不会跨 DB 实例
Revel + GORM 场景下最常踩的坑是:以为 db.Transaction(func(tx *gorm.DB) error { ... }) 能自动把所有子操作路由到同一分片。其实不会——如果你在事务块里又 new 了一个 DBShard2 实例去查数据,它就完全脱离了当前事务上下文。
- 事务只能在一个
*gorm.DB实例内生效,跨分片事务需靠应用层补偿或最终一致性 - 在 filter 中绑定的
c.TX应该是未 begin 的干净实例;service 层调用c.TX.Begin()后,必须把返回的*gorm.DB显式传给所有后续操作 - 用
defer tx.RollbackUnlessCommitted()替代裸Rollback(),防止 panic 导致事务残留 - Revel 的 panic recovery 默认会 recover 并返回 500,但不会自动 rollback——这点必须自己 wrap
真正麻烦的从来不是“怎么连上数据库”,而是“怎么让每次请求都用对连接、不出错、不泄漏、不串库”。Revel 的 filter 机制和 GORM 的实例可复制性,刚好能配合出一套轻量但可控的方案。别省那几行代码去封装一个“万能 DB 工厂”,把分片逻辑、事务边界、context 传递全揉进一个函数里,后期 debug 时你会回来重读这篇的。


















