★ 事务:Transaction 回调与错误回滚
结论:GoFrame 推荐 g.DB().Transaction(ctx, fn) 回调式事务:回调返回 nil 自动提交,返回 error 自动回滚;事务内必须使用回调传入的 tx(或由其派生的操作),严禁直接使用全局 g.DB(),否则会脱离事务造成“假事务”。
一、核心要点
- 提交/回滚自动完成:业务代码不写 Commit/Rollback,只管“成功返回 nil,失败返回 error”。
- 事务内操作对象是 tx:回调参数 tx 提供与 DB 一致的链式 API。
- ctx 透传:回调内所有操作续用框架传入的 ctx,保证链路与事务上下文一致。
- 手动模式:g.DB().Begin() 拿 tx,自行 Commit/Rollback,适合事务边界不规整的场景(少用)。
- 嵌套事务:回调内再开 Transaction 会走 SavePoint 机制(具体以官方文档为准)。
代码示例
err := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
// 扣余额(必须用 tx)
if _, err := tx.Model("account").Ctx(ctx).
Where("uid", 1).Increment("balance", -100); err != nil {
return err // 回滚
}
// 记账
if _, err := tx.Model("account_log").Ctx(ctx).Insert(g.Map{
"uid": 1,
"delta": -100,
}); err != nil {
return err // 回滚
}
return nil // 自动提交
})
if err != nil {
g.Log().Error(ctx, "转账失败", err)
}
常见追问 / 记忆点
- 追问:回调里误用 g.DB().Model 会怎样?→ 该操作不在事务内,可能“扣款成功但日志失败”却整体已提交,是最典型的事故点。
- 追问:忘记 return err 会怎样?→ 回调返回 nil,事务被误提交。
- 追问:回调内 panic 怎么办?→ 框架会处理回滚(是否继续向外抛出以官方文档为准),业务侧仍建议 recover 兜底。
- 记忆点:一切用 tx、错误必 return、ctx 别断传——“Transaction 帮你 Commit,但回滚只认 return 的 error”。