Revel 框架整合 GORM 必须手动管理 *gorm.DB 生命周期:仅在 init 阶段调用一次 gorm.Open(mysql.Open(dsn), &gorm.Config{}),显式设置连接池参数、探活;多库需动态注入请求级实例,模型方法须显式传参,事务需统一 commit/rollback 防悬挂。

Revel 框架不内置 ORM,整合 GORM 必须手动管理 *gorm.DB 生命周期,不能全局裸用、不能请求中反复 gorm.Open(),否则高并发下必 panic 或连接错乱。
gorm.Open() 必须传 driver 实例,不是 DSN 字符串
常见错误是直接把 MySQL 连接字符串传给 gorm.Open(),结果报 "invalid connection" 或空指针 panic。GORM v2+ 要求第一个参数是 driver 封装后的函数,不是 raw string。
- 必须显式导入驱动:
"gorm.io/driver/mysql" - DSN 字符串要包含
parseTime=True&loc=Local,否则time.Time字段解析失败(零值或invalid time format) - 正确写法:
gorm.Open(mysql.Open(dsn), &gorm.Config{}),其中mysql.Open(dsn)是必需的封装层 - 别在 controller 里调用它——
gorm.Open()返回的是带连接池的长期实例,只应在app/init.go的init()或AppInit()中初始化一次
连接池参数必须通过 db.DB().SetXXX() 单独设置
gorm.Config{} 只控制 ORM 层行为(如日志、命名策略),不触达底层 *sql.DB。若不显式配置,MaxOpenConns 默认为 0(即系统限制),容易触发连接耗尽或 TLS 握手超时。
- 初始化后立刻调用:
db.DB().SetMaxOpenConns(50)和db.DB().SetMaxIdleConns(20) - 建议加一次探活:
db.Exec("SELECT 1"),避免首请求因 DNS 或网络抖动失败 - 多库场景(主从/分片/租户)必须为每个
*gorm.DB独立配置,不能复用同一变量
DB 实例不能全局共享,必须按请求注入上下文
把 var DBMaster *gorm.DB 直接 import 到 model 层并调用 DBMaster.Create(),看似能跑,实则并发下事务错乱、Preload 失效、甚至 panic:"invalid memory address"。
- 正确路径:在 filter 中用
c.Params.Set("db", dbInstance)注入当前请求对应的实例 - 注册 filter:
revel.InterceptFunc(dbFilter, revel.BEFORE),确保在 controller 执行前完成绑定 - controller 中取用:
db := c.Params.Get("db").(*gorm.DB),类型断言不可省略 - 多租户或分片时,filter 内应根据 header、path 或 session 动态选库,而非硬编码
DBMaster
模型方法必须显式接收 *gorm.DB 参数
模型层不能 import 全局 DB 变量,也不能在 struct 方法里隐式依赖 app.DB。GORM 链式调用(如 db.Where().First())返回的是新拷贝的 *gorm.DB,若传参错误,操作会静默打到错误库上。
- 定义函数签名时显式传参:
func GetUser(db *gorm.DB, id uint) (*User, error) - 软删除需在模型字段加 tag:
DeletedAt gorm.DeletedAt `gorm:"index"`,Revel 不自动识别该字段 -
Create()后务必检查result.Error和result.RowsAffected,GORM v2 可能err == nil但实际未写入(如唯一约束冲突且未启用AllowGlobalUpdate) - 批量插入慎用
CreateInBatches(),它内部实现有边界条件缺陷(截至 2026 年 10 月 17 日)
最易被忽略的是 panic 恢复路径:Revel 默认 recover 并返回 500,但若事务已在 filter 中开启,却没在 defer 里 tx.Rollback(),就会留下悬挂事务。务必在 controller handler 结束前统一 commit/rollback,并在 panic filter 中补 rollback。











