消失的数据——带你真正了解Spring的事务原理

2 篇文章 0 订阅
1 篇文章 0 订阅

事情的起因是我最近写了一个多数据源的工具模块, 本来很愉快的运行着但当我把@Transactional这个大家耳熟能详的家伙放在我的方法上时灾难发生了,我的数据源切换失效了。这可急煞老夫了,迅速查看源码找到了罪魁祸首—DataSourceTransactionManager,它是Spring的默认事务管理器,在有事务时它的doBegin方法会把数据库连接缓存下来, 下次直接使用该连接, 而不是重新获取

就这样我开始了自定义分布式事务管理器之路,所谓磨刀不误砍柴工,我心想正好先研究研究Spring的原生事务还有传播属性,但当我开始将理论和实践相互验证的时候,诡异的情况出现了。众所周知,Spring有七大事务传播级别,默认是PROPAGATION_REQUIRED,它们如下

 

 看着官方的描述,我推论者方法的传播级别以及其结果,并用实践给与验证

我设计了两个方法TestService.testInsertPaymentA和TestProvider.testInsertPaymentB(以下简称A和B)

A调用B, 在调用B之前它向ATable插入100条数据, B中向BTable插入100条数据

从图中也可以知道我手动抛出异常制造回滚情形,当使用默认传播级别时一切都如预料,只要手动throw异常那么ATable和BTable都不会有数据,无异常时两张表都有数据。但我忽然灵光一闪, 传播级别是被调用时的传播级别,如果我把A方法的传播级别改为Propagation.NEVER时会怎么样呢?按道理A应该以非事务运行,B应该开启一个事务,这样我在A

抛出一个异常ATable和BTable应该都有数据的吧。结果一看BTable有数据,嗯这很合理。但是ATable的数据不翼而飞了,表中空空如也。我当时就是有很多问号了,这是啥子情况嘛,我的数据咧。更诡异的是在后面,我将抛异常去掉,让A和B都正常执行,然后发现ATable还是没有数据。???当现实和想象不一致的时候,一定是你的理解有了问题,接下来我就开始了漫长的源码剖析之路。

当A没有@Transactional注解时,会发现插入一条数据库就即时可见一条,在mybatis熟悉的SqlSessionTemplate中发现了SqlSessionInterceptor

很典型的动态代理, 对于没有@Transactional注解的方法,它会对其中的数据库操作每一次进行提交, 它调用了

DefaultSqlSession.commit

 进而又调用了BaseExecutor.commit

进而调用了 SpringManagedTransaction.commit, 在这里面真正进行了数据库连接的事务提交

总结起来的调用就是SqlSessionInterceptor.invoke -> DefaultSqlSession.commit -> BaseExecutor.commit -> 

SpringManagedTransaction.commit -> connection.commit()

 当A有@Transactional注解时整个情况就会复杂很多,先说SqlSessionInterceptor这时候就不会拉起 DefaultSqlSession.commit,数据也就不会真的提交, 它会进入到事务拦截器TransactionInterceptor的invoke中,

而这是一条更漫长的路。

进入 TransactionAspectSupport.invokeWithinTransaction, 忽略无关细节, 我们去看createTransactionIfNecessary

再次忽略次要代码,看getTransaction方法

进入AbstractPlatformTransactionManager.getTransaction

 

这个方法很明显和我们消失的数据有关了, 如果是PROPAGATION_NESTED、PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED会新起事务,执行startTransaction

会执行事务管理的预处理操作doBegin(事务管理器必然有把connection的autoCommit关闭的操作, 手动提交的必须代码) 

DefaultTransactionStatus也绑定了事务,这里就是关键点,对于NEVER来说它不会绑定事务即transaction为空

让我们回到TransactionAspectSupport,当执行完操作(invocation.proceedWithInvocation())后会执行commitTransactionAfterReturning方法

 让我们来看看数据是怎么提交的

 

进入AbstractPlatformTransactionManager.commit,忽略无关代码它会执行processCommit方法

 终于来到了最终地点

 如图, 在提交时会判断当前是否拥有事务

 正是之前绑定的事务对象,如果有的话此时就会进行提交了,但对于NEVER来说它没有事务它永远进入不了这个方法,这意味着它自己所有的数据操作都没法提交到数据库,它做的一切都是徒劳的。

为什么REQUIRED可以,因为A和B是共享的一个事务,B执行完后不会走提交,但A执行完后会提交会把当前数据源所有操作都提交(A和B使用的是一个数据库连接),也就是说其实这里走了两遍,A和B都有自己的切面也都会走这段逻辑。

以此类推,A如果是SUPPORTS或者NOT_SUPPORTED它的数据也会"消失", 因为它们也不会新开启自己的事务。尽管此种用法在实际中使用的很少, 但不排除有人会因为对事务执行的不了解而进行错误的使用,只有深刻理解了原理,才能在使用中规避一切未知做法可能导致的坑。

保持好奇,相信事实。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值