Spring Boot项目多数据源事务不生效的问题
作者:互联网
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()方法了,这里面的代码也看不懂了。
但是到这里我们可以得出以下结论:
1.我们的事务配置没有问题;
2.从上面的流程可以发现,我们的事务从创建,到执行业务逻辑,再到捕捉异常走回滚逻辑,都是正常执行了的,而且最后调用了Connection对象的rollback()方法去执行回滚操作。也就是我们的事务是执行了回滚操作的。
我们的事务流程是没有问题的。
可是为什么没有回滚成功?
难道是回滚过程发生了异常?并没有。在回滚抛异常的这一行打断点,这一行并没有执行。
思考:
1.事务的执行过程似乎没有问题;
2.我们项目里事务的配置应该也没有问题;
3.为什么其他的地方可以回滚,这里不能?(其他地方的事务配置和这个是一样的);
4.这里和其他地方有什么不一样的地方?
答案是:这里配置了多数据源,其他地方没有多数据源。
猜想:
是多数据源导致事务回滚失效的吗?
猜想的依据:
1.只有这里配置了多数据源;
2.前面doRollback()方法源码的逻辑是:先获取数据源对应的事务管理器,再通过事务管理器获取数据库连接对象,最后调用连接对象Connection的rollback()方法rollback()方法。
再猜想:
和事务管理器有关吗?还是获取的数据库连接对象有问题?还是事务管理器获取的不对?
我们确实是多数据源,连接多个数据库,那么对应的事务管理器确实应该不止一个,那么不同的管理器里拿到的数据库连接也是不同数据库的连接?
也就是事务的执行其实是靠事务管理来管理和执行的??
好多问号!!!!
如果是事务管理器的问题,我们能指定事务开始的时候使用哪个事务管理器吗?
如果可以,怎么做呢?
你在写代码的时候,最常用的开启一个事务的方式是什么?使用Spring 的 @Transactional 注解啊!
看一下@Transactional注解的源码:
还真的可以(虽然没这么干过)。它里面有个属性:string类型的 transactionManager (这不就是事务管理器吗!!)
那就尝试一下:
首先我们需要将事务管理器注入到Spring容器,给它指定一个BeanName,以便我们能拿到它。
在需要回滚的方法上使用事务管理器:
1.指定事务管理器;
2.抛RuntimeExcepttion异常;
3.执行代码,查询数据库,发现事务回滚成功了。
至此,这个问题的答案和前面的猜想已经基本一致了。一言以蔽之:多数据源下,一个事务应该由哪个数据源对应的事务管理器去管理。
这就是这个问题的原因。
继续思考:
既然是多数据源,也就意味着我们会有同时操作不同数据库的需求。这种情况下如何保证事务都正确回滚?
写两遍@Transactional注解肯定是不行的,编译不通过的。而且@Transactional注解的源码里transactionManager属性是String类型,不是String[]数组类型,不能写多个值。
好像又遇到问题了,原生注解实现不了。怎么办呢?
可以尝试自己写个自定义注解,实现上面的功能。感觉是可行的。可是怎么实现呢?
说实话,没思路,我不会了。我只能想自定义注解到这里了!!!!我也不知道该怎么实现这个功能。
那就百度吧,毕竟网友里都是大神。
spring boot 2.1学习笔记【八】SpringBoot 2 多数据源,多数据源事务 - 程序员大本营
看到了这两篇文章,他们提供了思路。
按照这里的思路,确实可以实现上面想要实现的功能。但是,在调试的过程中,又遇到了其他问题:
操作A数据库的事务确实回滚了,操作B数据库的事务还是没有回滚。。。。。
最后瞎猫碰上死耗子:
手动注入默认的defaultJdbcTempalte,并使用@Primary注解标注它是主数据源对应的jdbcTemplate;在默认数据源对应的TransactionManager上也用@Primary注解标注为主数据源对应的事务管理器。
操作完之后,所有事务都能回滚了。
这个原因是什么?是什么关联,我也不清楚,不知道看到这里的网友有什么想法??
总结:
1.这个问题最后的解决,我只想到了一部分,具体实现我是没想到,至少一时半会我是想不出来的。最后还是得靠百度,靠网友的智慧。
2.当我们找到了问题的原因所在,解决这个问题的方式经不是最重要的了。
我觉得前面debug的过程是这次最重要的收获,在这个过程中,可以看到事务的创建,执行,异常后回滚,这一套流程的执行过程;回滚的条件是RuntimeException和Error;用@Transactional注解指定事务管理器。。。。。
遇到问题勇敢去debug源码,总会找到一些灵感。
3.可能这里面遇到的没有想明白的问题,估计只有相关源码才能给出答案了!!!!
4.这是个技术上的问题,和具体业务扯不上关系,所以还是把它记下来吧。
标签:事务,管理器,数据源,Boot,回滚,Spring,rollback,方法 来源: https://blog.csdn.net/sungaochao/article/details/120776354