记一次Springboot事务超时不回滚的分析过程
在Springboot中,我用的xml进行事务管理,DataSourceTransactionManager作为事务管理器,配置了事务控制在Service层;在事务管理器中,配置了defaultTimeout事务超时时间为5秒,开始的认知里,如果事务执行超过5秒,Service层未执行完成,则认为会回滚事务
事务管理器的配置如下
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/><property name="defaultTimeout" value="5"/></bean>
事务控制在Service层配置
<aop:config><aop:pointcut id="txPointcut" expression="execution(* com.miso.infrastructure.asynctask.service.*.*(..))" /><aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/></aop:config>
开始测试
我的测试程序执行顺序是这样的:Controller->ServiceA->ServiceB->Dao
从Controller调用ServiceA开始,到ServiceA执行结束,是一个事务控制生命周期,如果在这个生命周期里,执行的时长超过事务管理器中配置的时间5秒,则事务回滚
为了能模拟延迟的效果,我在ServiceB调用Dao之前和之后,分别增加了Sleep的效果,让程序睡10秒
场景一:在调用Dao之前,增加让程序睡10秒
public AsyncTaskEntity createAsyncTask(AsyncTaskEntity asyncTaskEntity) {Thread.sleep(10000);asyncTaskDao.createAsyncTask(asyncTaskEntity);return asyncTaskEntity;}
场景二:在调用Dao之后,增加让程序睡10秒
public AsyncTaskEntity createAsyncTask(AsyncTaskEntity asyncTaskEntity) {asyncTaskDao.createAsyncTask(asyncTaskEntity);Thread.sleep(10000);return asyncTaskEntity;}
在我的第一认知里面,不管是场景一还是场景二,这两种都应该是超时回滚的,但实际测试结果出呼我所料,第一种场景事务回滚,而第二种场景没有回滚,第二种场景的即使超过了超时时间5秒,但是事务依然是提交的,在数据库中可以看到有新增的记录,是不是很意外,为什么事务执行时间超过了5秒,事务没有回滚,而是提交成功了呢?
开始分析
从这个报错的信息中,我们可以看到有一个抽象类ResourceHolderSupport,里面有个checkTransactionTimeout方法,这个方法里面扫了一个事务超时的异常
ResourceHolderSupport.checkTransactionTimeout源码如下,如果deadlineReached为true,则执行事务回滚,并抛事务超时异常
private void checkTransactionTimeout(boolean deadlineReached) throws TransactionTimedOutException {if (deadlineReached) {setRollbackOnly();throw new TransactionTimedOutException("Transaction timed out: deadline was " + this.deadline);}}
从堆栈中可以看到,调用checkTransactionTimeout方法的是getTimeToLiveInMillis方法,ResourceHolderSupport.getTimeToLiveInMillis源码如下
public long getTimeToLiveInMillis() throws TransactionTimedOutException{if (this.deadline == null) {throw new IllegalStateException("No timeout specified for this resource holder");}long timeToLive = this.deadline.getTime() - System.currentTimeMillis();checkTransactionTimeout(timeToLive <= 0);return timeToLive;}
从源码中可以看到,deadline的时间减去当前系统时间,如果小于0,则deadlineReached为true,事务进行回滚
那什么时候会调用ResourceHolderSupport.getTimeToLiveInMillis方法呢,从堆栈中可以看到整个的调用链如下:
在执行SimpleExecutor.prepareStatement方法时,会从Spring事务管理器中获取超时时间,最终调用到ResourceHolderSupport.getTimeToLiveInMillis
从这个调用链可以看出,Spring是在调用Dao的时候,去判断事务超时时间的,如果执行时间超过事务超时时间,则执行回滚,并抛出TransactionTimedOutException异常
因此到这里就可以解释了为什么上面 场景二:在调用Dao之后,增加让程序睡10秒不会回滚,因为dao层已经执行完成,后面不会进行超时判断了,除非在后面有另一个DAO的操作(前提是:同一个事务)
这里还有一个问题,ResourceHolderSupport.getTimeToLiveInMillis中的deadline.getTime()这个时间是在什么时候设置上的呢,我们注意到在ResourceHolderSupport中有个setTimeoutInMillis方法,这里有设置deadline的值,源码如下
那什么时候调用setTimeoutInMillis方法呢,在方法里打判断,查看堆栈信息,发现如下调用链
从调用链可以看出,Controll调用ServiceA之前,Spring框架会调用事务管理器DataSourceTransactionManager的doBegin方法开启事务,并在方法里面调用ResourceHolderSupport.setTimeoutInSeconds方法设置超时时间,之后才调用到ServiceA
因此在Controller调用ServiceA之前,Spring框架就会开启事务,并设置事务超时时间