spring业务事务使用中的坑
前言
spring事务还是要单独拿起来说,大部分的开发使用都是用@Transactional
去控制事物,有经验的会加上参数rollbackFor = Exception.class
,正常情况下不会出问题,但是结合某种特殊的业务场景下就会出现不可预知的bug。
怎么说呢?有这样一个业务场景:我们需要向某个公司去同步数据,同步完成以后,修改表里的字段push_status
为1,代表这条数据已经同步过了。正常的思路,就是查出数据、组装符合接口的数据、请求同步接口、修改字段,然后在方法上加一个事物注解,就结束了。
但是,这里会有个坑!具体什么坑看下面模拟
模拟业务
这里为了更好的说明这个坑,所以模拟业务,然后需要准备一些东西
准备
新建个测试表,这里随便创建一个接
1 | CREATE TABLE `test` ( |
新建模拟接口
新建service层,进行dao层插入操作,这里的100次循环,我们模拟需要同步的数据,通过syncDataRequest模拟同步过去的数据
测试
这里正常访问接口一口,看数据量,模拟正常业务请求
全部处理成功,在查看数据库
没有问题,这是正常的情况,并不能说明什么。
我们模拟异常的情况,模拟之前先清空test表的数据,我们写个逻辑,如果i是80就抛出异常,模拟异常情况,再跑一遍
请求
在处理完79id,80id失败了,抛出异常,然后去查看数据库数据,理论数据库应该会有80条数据,因为是从0开始的
哎,数据并没有插入进去,这。。。不慌,跑一手路。。。
其实道理很简单,抛出异常,被@Transactional
注解捕捉到了,于是他直接全部回滚,正常业务是没问题,但是我们的业务是同步一次数据并修改数据库,代表这条数据已经同步了,绝对不能回滚,毕竟我们已经同步过去的数据,不能说还要告诉人家公司,我们业务异常了,你给我回滚一下,公司分布式事务吗?
这个时候,其实有很多办法去解决,详说一下
解决
定义成功失败状态
定义请求的状态,如果成功就修改数据库,如果失败就不处理,并且try catch一下
模拟请求一下
发现80的异常并没有被处理,在查看数据库
没有问题,皆大欢喜,打完收工!这就完了?并不是,相信大部分人是这样做的。这里会存在一个问题。
- 我们如何保证同步完数据,修改数据库一定成功
这玩意没法保证啊,万一,在你请求完数据库,数据库抽风,就是插入失败,或者数据库IO突然变高,插入数据库超时抛出异常,那完犊子了,前面的数据修改全部回滚了,这种问题是致命的,导致数据不同步。
当然有大聪明可能会想,我一次查一个数据,同步一次不就好了?这里想说,单走一个6
那么,如何解解决的?
提前插入法和手动提交事务
我们提前执行修改数据库,然后在同步完数据的时候,在手动提交事务不就好了,然后每次循环都是这样操作,每次循环,开始事物,在结尾决定是否提交事务或者回滚事务。
这里需要用到事务api说下:
注入事务管理对象
定义一个事物信息,并交给事物管理器
try catch业务代码,处理事物的提交和回滚
这样我们就可以手动的处理事物,并且我们是先去修改数据库,并未提交事务,如果请求成功,那就提交事务,如果请求失败,那就回滚失败,这样保证了业务处理的原子性。
具体测试,我就不测试, 然后因为api麻烦的原因,于是,写了一个工具类
注意:手动的事物,需要提交几次,就要开启几次,就是如果遇到循环手动提交事务的逻辑,就把开启事物写在循环里,否则异常
工具类:
1 | /** |