Revel 框架不内置 MongoDB 支持,必须用 revmgo 等第三方包桥接;直接全局复用 mgo.Session 会导致 panic、连接泄漏和并发错误,因其非线程安全且需按请求 Copy/Close。

Revel 框架本身不内置 MongoDB 支持,必须通过第三方包(如 revmgo)桥接,否则会遇到 session is nil、连接泄漏、并发 panic 等问题。直接手写 session 管理极易出错,不推荐。
为什么不能直接 new mgo.Session 并全局复用
Revel 是基于拦截器(interceptor)的请求生命周期框架,每个 HTTP 请求应拥有独立、可追踪的 MongoDB session,但 mgo.Session 不是线程安全的,且 Copy() 后必须显式 Close()。全局单例或未受控复用会导致:
-
panic: session already closed—— 多个 action 共享 session 时被提前关闭 - 连接池耗尽 ——
mgo.DialWithInfo创建的 session 默认不自动归还到池 - 事务失效 —— MongoDB 的
StartSession()无法与 Revel 的 action 生命周期对齐
使用 revmgo.ControllerInit() 和 AppInit() 的正确顺序
revmgo 是目前最轻量、与 Revel 耦合最自然的 MongoDB 集成方案,它靠两个初始化钩子完成 session 生命周期绑定:
- 在
app/init.go的init()函数中调用revmgo.ControllerInit()—— 注册拦截器,确保每个 controller action 前后自动Copy()和Close()session - 在
revel.OnAppStart中调用revmgo.AppInit()—— 读取app.conf中的revmgo.dial,建立 master session,并设置db.spec(决定是否复用 session 或每次新建) - 必须确保
revmgo.AppInit()在revel.Router加载之后、任何 controller 实例化之前执行,否则拦截器注册失败
Controller 中嵌入 MongoController 的陷阱
你必须同时嵌入 *revel.Controller 和 revmgo.MongoController,即使后者已内嵌前者:
-
revmgo.MongoController内部确实有*revel.Controller字段,但 Revel 的反射逻辑只检查 controller 结构体**第一层字段**是否为*revel.Controller,不会递归深入 - 漏掉
*revel.Controller会导致404或panic: controller has no revel.Controller embedded - 正确写法:
type Account struct { *revel.Controller revmgo.MongoController }
Dal 层如何安全使用 c.MongoSession
c.MongoSession 是由 revmgo 在 action 进入时自动 Copy() 出来的 session,它已绑定当前请求上下文,但需注意:
- 它不是全局 session,不能跨 goroutine 传递;若启动后台协程操作数据库,必须重新
c.MongoSession.Copy() - 不要在 Dal 构造函数里做
defer session.Close()——revmgo已在拦截器中统一处理,重复 Close 会 panic - 如果需要长连接(如监听 change stream),必须用
revmgo.MasterSession,而非c.MongoSession - 示例:
func (c *Account) HandleRegister(user *models.MockUser) revel.Result { dal := models.NewDal(c.MongoSession) // 直接传入,无需 Copy() err := dal.RegisterUser(user) return nil }
真正容易被忽略的是:revmgo 的 db.spec 配置项默认是 single(复用 master session 的 copy),但在高并发写入场景下,建议设为 multi 避免 session 竞态;这个开关没有文档强调,只能翻源码确认。











