Revel 通过 revmgo 自动管理 mgo.Session 生命周期:在 app/init.go 中注册 BeforeAction 和 AfterAction 拦截器,使每个请求克隆并关闭 session;Controller 必须同时嵌入 *revel.Controller 和 revmgo.MongoController;Dal 层直接复用 c.MongoSession,避免 Copy() 和全局持有。

Revel 框架本身不内置 MongoDB 支持,必须靠第三方库桥接;最常用、最轻量、也最贴合 Revel 生命周期的是 revmgo —— 它不是万能胶,但对标准 CRUD 场景足够直接、可控,且避免了 session 泄漏和并发冲突。
怎么让 Revel 自动管理 mgo.Session 生命周期
revmgo 的核心价值在于把 mgo.Session 的获取、克隆、关闭绑定到 Revel 的请求周期。它靠两个 intercept 函数实现:revmgo.BeforeAction 在 action 执行前克隆一个线程安全的 session 并挂到 controller 上,revmgo.AfterAction 在 action 返回后关闭该 session。
必须在 app/init.go 中注册:
func init() {
revel.Intercept(revmgo.BeforeAction, revel.BEFORE)
revel.Intercept(revmgo.AfterAction, revel.AFTER)
}
否则 c.MongoSession 始终为 nil,后续所有数据库操作都会 panic。
常见错误现象:
- 调用
c.MongoSession.Copy()时 panic: "invalid memory address or nil pointer dereference" - 没报错但数据写不进库 —— 实际是 session 没被正确克隆,用的是未初始化的空指针
Controller 必须显式嵌入 *revel.Controller 和 revmgo.MongoController
即使 revmgo.MongoController 内部已经嵌入了 *revel.Controller,你也得在自定义 controller 里再写一遍 *revel.Controller。这不是冗余,是 Revel 反射机制的硬性限制:它只检查 controller 结构体的第一层字段,不会递归扫描嵌套结构体里的嵌入字段。
正确写法:
type Account struct {
*revel.Controller
revmgo.MongoController
}
错误写法(会导致 c.Render、c.Redirect 等方法不可用):
type Account struct {
revmgo.MongoController // 缺少 *revel.Controller,action 无法被 Revel 识别为合法 handler
}
参数差异影响:
- 缺
*revel.Controller→ Revel 不认为这是个 controller,路由匹配失败,返回 404 或 panic - 缺
revmgo.MongoController→c.MongoSession不存在,无法访问 DB
如何安全地在 Dal 层复用 session 并避免阻塞
mgo.Session 本身不是 goroutine-safe 的,但 revmgo 提供的 c.MongoSession 是每次请求独享的克隆体,所以你在 Dal 构造函数里直接传入它即可,无需再调用 .Copy()。
推荐 Dal 定义方式:
type Dal struct {
session *mgo.Session
}
func NewDal(session *mgo.Session) *Dal {
return &Dal{session: session}
}
func (d *Dal) RegisterUser(user *models.User) error {
return d.session.DB("leanote").C("users").Insert(user) // 直接用,不用 Copy()
}
性能影响注意点:
- 不要在 Dal 方法内反复调用
session.Copy()—— 这会创建新连接,增加连接池压力 - 不要把
session存成全局变量或长期持有 —— 会引发连接泄漏和读取脏数据 - 如果需要跨多个 collection 操作,用同一个
d.session实例,它支持链式调用
配置项 revmgo.dial 和 db.spec 的实际含义
revmgo.dial 是标准 MongoDB 连接字符串,例如:mongodb://localhost:27017/leanote。带认证时务必 URL 编码用户名和密码。
db.spec 控制 session 复用策略,只有两个合法值:
-
db.spec = 0(默认):每个请求都新建 session 克隆,最安全,适合大多数场景 -
db.spec = 1:复用单个 session(不推荐),可能引发并发读写冲突,仅用于极低 QPS 的调试环境
容易踩的坑:
把 db.spec = 1 误配成生产环境配置,导致偶发性数据覆盖或查询返回旧快照 —— 因为 mgo 默认启用monotonic 模式,session 复用时可能命中 stale connection。
真正复杂的地方不在代码,而在于你是否意识到:c.MongoSession 是请求级临时对象,不能存进 struct 字段长期持有,也不能传给异步 goroutine —— 那些地方必须自己 .Copy() 并确保 .Close()。


















