基本介绍
使用GoFrame ORM
组件进行事务操作非常简便、安全,可以通过两种操作方式来实现。
- 常规操作:通过
Begin
开启事务之后会返回一个事务操作对象*gdb.TX
,随后可以使用该对象进行如之前章节介绍的方法操作和链式操作。常规操作容易漏掉关闭事务,有一定的事务操作安全风险。 - 闭包操作:通过
Transaction
闭包方法的形式来操作事务,所有的事务逻辑在闭包中实现,闭包结束后自动关闭事务保障事务操作安全。并且闭包操作支持非常便捷的嵌套事务,嵌套事务在业务操作中透明无感知。
接口文档: https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#TX
相关文档
15 Comments
primexiao
不管事务是 commit 或者 rollback,这个事务都结束了;所以获取到的事务连接 tx 不要想着保存了供后续使用
朱华 Hunk
现在ORM生成的DAO,似乎无法自动完成事务嵌套。比如,最早我之前实现了一个 aService.GetInfoByID,这里面只有一个FindOne操作。然而,我需要新设计一个UpdateInfo,逻辑为更新后,再查询。而查询是调用了GetInfoByID方法。即,其实大概逻辑应该是:
begin();
update....
GetInfoByID()
...
commit;
当出现这类嵌套需求时,对于GetInfoByID,它其实并不知道外层会有事务调用。所以在GetInfoByID内部,其实并不会受到事务影响。这样就出现一个问题,我用GetInfoByID获取到的数据,并不是事务内更新过的数据。这样就引起了一个BUG。但是,若我想事务作用于GetInfoByID内部,那就必须传递tx进去。
还有一种情况,存在一个Update1()的方法,其内部使用了事务。这时又需要编写一个Update2(),在Update1()的基础上,再额外增加一些其它更新操作。保证Update2的操作是事务的。但由于Update1内部已经有一个事务。那么,在Update2中开了事务后,两个嵌套事务能否一起提交与回滚?
对于这些情况。GF有无考虑?若有考虑,那应该如何处理?
郭强
你好啊,以下是我的个人看法,欢迎交流讨论。
tx
对象的输入是合理的。Transaction
闭包的方式来管理事务,内部会自动执行Commit
或者Rollback
方法。ORM
组件能够解决的。朱华 Hunk
若是每次使用事务,都必须如此操作。那么很多业务级的方法就很难去封装及维护。经常存在某个业务逻辑,是由更小的多个业务逻辑组合而成。而为了原子性,则需要在这个业务中启用事务。
但是,现在问题来了。若更底层的逻辑不是由当前调用者维护的,这些方法的内部处理对外则是透明的。若我想实现所有底层的业务处理完毕,再统一提交事务,这样就很困难了。因为现在没办法在更高层逻辑中开启事务。
同时,就算团队约定好每个方法必须支持事务传入,但是,这样的代码量也会增加非常多。并不利于快速开发。
这个问题,我也一直在考虑如何在业务层能解决,但是发现,还是比较困难。可能还是需要调整ORM底层,或更换框架的使用方式(比如放弃sevice, dao的单例使用)。
现在我是自己封装了一个 Transaction,通过获取堆栈信息来识别当前的协程ID。然后完成的嵌套处理。不过,这样依然有缺陷,主要问题还是,若子方法中的Transaction未覆盖所有数据库操作,那么这些操作将不会被加入上一级的事务操作中。比如我们的一个子逻辑为“先查询,再事务更新”。在父逻辑中,是会执行 事务更新数据后再调用这个子逻辑(事务中调用)。这样子逻辑中由于查询未在事务中,则会导致查出来的数据出现不一致,引起逻辑BUG。思来想去,还是只有ORM底层改造能解决这个问题。
郭强
大概明白你的意思了,你这个痛点确实只有从
orm
入手才能解决。这个时候
service
的对象不能使用dao
的数据对象了,而是应当动态生成service
对象,service
依赖的dao
对象应当支持依赖注入,这样才能满足你的要求。不过如何优雅的设计可能会比较麻烦,你可以提个issue
,我后续考虑下这个改进,你也可以直接在goframe的个人空间上写个wiki
文章描述你的设计,我们一起讨论。朱华 Hunk
写了下我自己折腾的东东。希望能给到一些帮助。^_^
关于ORM的事务嵌套的业务级调整(不会影响GF本身) - GoFrame官网 - 类似PHP-Laravel, Java-SpringBoot的Go企业级开发框架
郭强
已改进,
v1.16
新版不再需要显示传递tx
参数,但是需要层级传递ctx
上下文变量。dabuge
事务嵌套确实有必要支持,项目维护的人稍微一多,事务嵌套就避免不了,要是不支持事务嵌套,会带来很大麻烦,可以参考一下 laravel 是怎么处理事务嵌套的
郭强
已支持。
tobin
事务嵌套,是在子事务开启时设置还原点,需保证是同一个连接
SAVEPOINT name1
在子嵌套事务中回滚,就是回滚到还原点
ROLLBACK TO SAVEPOINT name1
commit提交时全部提交
郭强
已支持嵌套事务。
primexiao
func (db DB) Transaction(ctx context.Context, f func(ctx context.Context, tx *TX) error) (err error)
从 v1.15 升级到 v1.16,发现 Transaction 多了 context.Context 参数。
问:实践当中这个 ctx 哪里来?
从 controller 层的 *ghttp.Request 一路传过来吗?这样的话,整个调用链的参数都要改,成本太大了
但默认的 context.TODO() 和 context.Background() 又是全局共用的,似乎不太适合http服务(看到有人说即使从它俩继承也不太好)
求解
郭强
推荐是从顶层即
r.Context()
返回的ctx
层级传递到下层。如果实在不想用ctx
,就传递默认的context.TODO()
或者context.Background()
。此外,其实GoFrame ORM
的Ctx
方法也支持nil
,不过Go
官方不推荐使用nil
传递ctx
,程序处理不好可能会在某些场景产生意想不到的问题。做Go
项目你会发现将第一个参数设置为ctx
参数是一个很好的习惯,项目越到后面你会发现越有必要,特别是针对于微服务项目来说。soloking
我使用sql server 时,使用orm的事务,发现当sql server 死锁后,sql server 会自动杀掉死锁的进程。然后这时不会回滚。
m.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
_, err := tx.Ctx(ctx).Exec(`SET XACT_ABORT ON`)
这样设置也无效。求解。
suidongpo
使用 pgsql ,不使用事务可以使用LastInsertId,但在事务中却不能使用LastInsertId,有什么解决办法吗?