Spring Boot项目多数据源事务不生效的问题

2,195 阅读6分钟

1.问题场景描述****

在方法中操作数据库,向表中插入一条数据;然后抛出RuntimeException,发现事务没有回滚,插入的数据依然提交到了数据库中。

2.问题查找及分析过程****

2.1 首先查看项目中是否配置了事务****

项目中确实做了事务相关的配置:

spring.transaction.expression=execution(* xxxxxxx.service.impl.Impl.(..))

那么这个配置是否生效呢?我们在执行insert后面手动抛出一个RuntimeException,看到如下的报错信息,说明异常确实抛出了,但是查询数据后发现,事务确实没有回滚。

2.2 没办法,打断点跟代码吧,看执行过程****

图片.png

我们根据报错的日志,找到 TransactionAspectSupport 和 TransactionInterceptor这两个类:

找到日志中报错的TransactionInterception的invok()方法,在这里打断点,

图片.png

 然后进入invokWithinTransaction()这个方法,就进入到了其父类TransactionAspectSupport的invokWithinTransaction()方法,我们在里面打断点继续跟踪执行过程:

图片.png

然后继续,我们进入异常后回滚事务的方法:completeTransactionAfterThrowing(txInfo, var17)

图片.png

跟到这里面,我们注意rollbackOn(ex)方法和txInfo.getTransactionManager().rollback()方法, 1.rollbackOn(ex)方法(在DefaultTransactionAttribute类里)的入参是一个异常类型,通过这个方法的源码我们可以发现,Spring事务默认只有在发生RuntimeException类型的异常和Error时才走下面的rollback()方法,也就是默认在捕捉到RuntimeException异常和Error时才执行回滚逻辑。(网上相关帖子都是这么说的,原因就在这段源码里)。

图片.png

2.rollback()方法:执行回滚操作,在抽象类AbstractPlatformTransactionManager里。

由于我们收到抛出了RuntimeException,所以符合回滚执行的条件,所以我们接着跟进rollback()方法里:

图片.png

这里面可以看到:先判断当前事务状态是否时完成状态,如果没有完成,继续执行processRollback()方法执行回滚操作,继续跟到这个方法里面:

图片.png

这个方法里其他的代码不用看,也看不懂,我们顺着断点最后跟进this.doRollback(status)方法,最终我们的代码走到了这一行。我们进入doRollback()方法(在DataSourceTransactionManager类里):

图片.png

这个方法的流程:

1).获取DataSourceTransactionManager对象txObject,个人理解,也就是获取数据源对应的事务管理器;

2).从事务管理器对象里获取数据库连接Connection对象;

3).调用Connection对象的rollback()方法;

我们再继续跟进Connection对象的rollback()方法:

找到DruidPooledConnection的rollback()方法:里面还是执行回滚操作。

图片.png

至此,

我觉得没有必要再跟下去了,已经到Connection对象的rollback()方法了,这里面的代码也看不懂了。

2.3 结论****

但是到这里我们可以得出以下结论:

1.我们的事务配置没有问题;

2.从上面的流程可以发现,我们的事务从创建,到执行业务逻辑,再到捕捉异常走回滚逻辑,都是正常执行了的,而且最后调用了Connection对象的rollback()方法去执行回滚操作。也就是我们的事务是执行了回滚操作的。

我们的事务流程是没有问题的。

可是为什么没有回滚成功?

难道是回滚过程发生了异常?并没有。在回滚抛异常的这一行打断点,这一行并没有执行。

图片.png

3. 思考:****

1.事务的执行过程似乎没有问题;

2.我们项目里事务的配置应该也没有问题;

3.为什么其他的地方可以回滚,这里不能?(其他地方的事务配置和这个是一样的);

4.这里和其他地方有什么不一样的地方?

答案是:这里配置了多数据源,其他地方没有多数据源。

猜想:****

是多数据源导致事务回滚失效的吗?

猜想的依据:

1.只有这里配置了多数据源;

2.前面doRollback()方法源码的逻辑是:先获取数据源对应的事务管理器,再通过事务管理器获取数据库连接对象,最后调用连接对象Connection的rollback()方法rollback()方法。

再猜想:****

和事务管理器有关吗?还是获取的数据库连接对象有问题?还是事务管理器获取的不对?

我们确实是多数据源,连接多个数据库,那么对应的事务管理器确实应该不止一个,那么不同的管理器里拿到的数据库连接也是不同数据库的连接?

也就是事务的执行其实是靠事务管理来管理和执行的??

好多问号!!!!

如果是事务管理器的问题,我们能指定事务开始的时候使用哪个事务管理器吗?

如果可以,怎么做呢?

你在写代码的时候,最常用的开启一个事务的方式是什么?使用Spring 的 @Transactional 注解啊!

看一下@Transactional注解的源码:

还真的可以(虽然没这么干过)。它里面有个属性:string类型的 transactionManager (这不就是事务管理器吗!!)

图片.png

那就尝试一下:****

首先我们需要将事务管理器注入到Spring容器,给它指定一个BeanName,以便我们能拿到它。

图片.png

在需要回滚的方法上使用事务管理器:

1.指定事务管理器;

图片.png

2.抛RuntimeExcepttion异常;

3.执行代码,查询数据库,发现事务回滚成功了。

至此,这个问题的答案和前面的猜想已经基本一致了。一言以蔽之:多数据源下,一个事务应该由哪个数据源对应的事务管理器去管理。

这就是这个问题的原因。

4. 继续思考:****

既然是多数据源,也就意味着我们会有同时操作不同数据库的需求。这种情况下如何保证事务都正确回滚?

写两遍@Transactional注解肯定是不行的,编译是不通过的。而且@Transactional注解的源码里transactionManager属性是String类型,不是String[]数组类型,不能写多个值。

图片.png

好像又遇到问题了,原生注解实现不了。怎么办呢?****

可以尝试自己写个自定义注解,实现上面的功能。感觉是可行的。可是怎么实现呢?

说实话,没思路,我不会了。我只能想自定义注解到这里了!!!!我也不知道该怎么实现这个功能。

那就百度吧,毕竟网友里都是大神。****

spring boot 2.1学习笔记【八】SpringBoot 2 多数据源,多数据源事务 - 程序员大本营

Spring Boot多数据源事务管理 - 程序员大本营

看到了这两篇文章,他们提供了思路。

按照这里的思路,确实可以实现上面想要实现的功能。