1.问题场景描述****
在方法中操作数据库,向表中插入一条数据;然后抛出RuntimeException,发现事务没有回滚,插入的数据依然提交到了数据库中。
2.问题查找及分析过程****
2.1 首先查看项目中是否配置了事务****
项目中确实做了事务相关的配置:
spring.transaction.expression=execution(* xxxxxxx.service.impl.Impl.(..))
那么这个配置是否生效呢?我们在执行insert后面手动抛出一个RuntimeException,看到如下的报错信息,说明异常确实抛出了,但是查询数据后发现,事务确实没有回滚。
2.2 没办法,打断点跟代码吧,看执行过程****
我们根据报错的日志,找到 TransactionAspectSupport 和 TransactionInterceptor这两个类:
找到日志中报错的TransactionInterception的invok()方法,在这里打断点,
然后进入invokWithinTransaction()这个方法,就进入到了其父类TransactionAspectSupport的invokWithinTransaction()方法,我们在里面打断点继续跟踪执行过程:
然后继续,我们进入异常后回滚事务的方法:completeTransactionAfterThrowing(txInfo, var17)
跟到这里面,我们注意rollbackOn(ex)方法和txInfo.getTransactionManager().rollback()方法, 1.rollbackOn(ex)方法(在DefaultTransactionAttribute类里)的入参是一个异常类型,通过这个方法的源码我们可以发现,Spring事务默认只有在发生RuntimeException类型的异常和Error时才走下面的rollback()方法,也就是默认在捕捉到RuntimeException异常和Error时才执行回滚逻辑。(网上相关帖子都是这么说的,原因就在这段源码里)。
2.rollback()方法:执行回滚操作,在抽象类AbstractPlatformTransactionManager里。
由于我们收到抛出了RuntimeException,所以符合回滚执行的条件,所以我们接着跟进rollback()方法里:
这里面可以看到:先判断当前事务状态是否时完成状态,如果没有完成,继续执行processRollback()方法执行回滚操作,继续跟到这个方法里面:
这个方法里其他的代码不用看,也看不懂,我们顺着断点最后跟进this.doRollback(status)方法,最终我们的代码走到了这一行。我们进入doRollback()方法(在DataSourceTransactionManager类里):
这个方法的流程:
1).获取DataSourceTransactionManager对象txObject,个人理解,也就是获取数据源对应的事务管理器;
2).从事务管理器对象里获取数据库连接Connection对象;
3).调用Connection对象的rollback()方法;
我们再继续跟进Connection对象的rollback()方法:
找到DruidPooledConnection的rollback()方法:里面还是执行回滚操作。
至此,
我觉得没有必要再跟下去了,已经到Connection对象的rollback()方法了,这里面的代码也看不懂了。
2.3 结论****
但是到这里我们可以得出以下结论:
1.我们的事务配置没有问题;
2.从上面的流程可以发现,我们的事务从创建,到执行业务逻辑,再到捕捉异常走回滚逻辑,都是正常执行了的,而且最后调用了Connection对象的rollback()方法去执行回滚操作。也就是我们的事务是执行了回滚操作的。
我们的事务流程是没有问题的。
可是为什么没有回滚成功?
难道是回滚过程发生了异常?并没有。在回滚抛异常的这一行打断点,这一行并没有执行。
3. 思考:****
1.事务的执行过程似乎没有问题;
2.我们项目里事务的配置应该也没有问题;
3.为什么其他的地方可以回滚,这里不能?(其他地方的事务配置和这个是一样的);
4.这里和其他地方有什么不一样的地方?
答案是:这里配置了多数据源,其他地方没有多数据源。
猜想:****
是多数据源导致事务回滚失效的吗?
猜想的依据:
1.只有这里配置了多数据源;
2.前面doRollback()方法源码的逻辑是:先获取数据源对应的事务管理器,再通过事务管理器获取数据库连接对象,最后调用连接对象Connection的rollback()方法rollback()方法。
再猜想:****
和事务管理器有关吗?还是获取的数据库连接对象有问题?还是事务管理器获取的不对?
我们确实是多数据源,连接多个数据库,那么对应的事务管理器确实应该不止一个,那么不同的管理器里拿到的数据库连接也是不同数据库的连接?
也就是事务的执行其实是靠事务管理来管理和执行的??
好多问号!!!!
如果是事务管理器的问题,我们能指定事务开始的时候使用哪个事务管理器吗?
如果可以,怎么做呢?
你在写代码的时候,最常用的开启一个事务的方式是什么?使用Spring 的 @Transactional 注解啊!
看一下@Transactional注解的源码:
还真的可以(虽然没这么干过)。它里面有个属性:string类型的 transactionManager (这不就是事务管理器吗!!)
那就尝试一下:****
首先我们需要将事务管理器注入到Spring容器,给它指定一个BeanName,以便我们能拿到它。
在需要回滚的方法上使用事务管理器:
1.指定事务管理器;
2.抛RuntimeExcepttion异常;
3.执行代码,查询数据库,发现事务回滚成功了。
至此,这个问题的答案和前面的猜想已经基本一致了。一言以蔽之:多数据源下,一个事务应该由哪个数据源对应的事务管理器去管理。
这就是这个问题的原因。
4. 继续思考:****
既然是多数据源,也就意味着我们会有同时操作不同数据库的需求。这种情况下如何保证事务都正确回滚?
写两遍@Transactional注解肯定是不行的,编译是不通过的。而且@Transactional注解的源码里transactionManager属性是String类型,不是String[]数组类型,不能写多个值。
好像又遇到问题了,原生注解实现不了。怎么办呢?****
可以尝试自己写个自定义注解,实现上面的功能。感觉是可行的。可是怎么实现呢?
说实话,没思路,我不会了。我只能想自定义注解到这里了!!!!我也不知道该怎么实现这个功能。
那就百度吧,毕竟网友里都是大神。****
spring boot 2.1学习笔记【八】SpringBoot 2 多数据源,多数据源事务 - 程序员大本营
看到了这两篇文章,他们提供了思路。
按照这里的思路,确实可以实现上面想要实现的功能。