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 多数据源,多数据源事务 - 程序员大本营

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

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

按照这里的思路,确实可以实现上面想要实现的功能。但是,在调试的过程中,又遇到了其他问题:

操作A数据库的事务确实回滚了,操作B数据库的事务还是没有回滚。。。。。

最后瞎猫碰上死耗子:

手动注入默认的defaultJdbcTempalte,并使用@Primary注解标注它是主数据源对应的jdbcTemplate;在默认数据源对应的TransactionManager上也用@Primary注解标注为主数据源对应的事务管理器。

操作完之后,所有事务都能回滚了。

这个原因是什么?是什么关联,我也不清楚,不知道看到这里的网友有什么想法??

总结:

1.这个问题最后的解决,我只想到了一部分,具体实现我是没想到,至少一时半会我是想不出来的。最后还是得靠百度,靠网友的智慧。

2.当我们找到了问题的原因所在,解决这个问题的方式经不是最重要的了。

我觉得前面debug的过程是这次最重要的收获,在这个过程中,可以看到事务的创建,执行,异常后回滚,这一套流程的执行过程;回滚的条件是RuntimeException和Error;用@Transactional注解指定事务管理器。。。。。

遇到问题勇敢去debug源码,总会找到一些灵感。

3.可能这里面遇到的没有想明白的问题,估计只有相关源码才能给出答案了!!!!

4.这是个技术上的问题,和具体业务扯不上关系,所以还是把它记下来吧。

  • 10
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值