Spring源码核心篇整体栏目
内容 | 链接地址 |
---|---|
【一】Spring的bean的生命周期 | https://zhenghuisheng.blog.csdn.net/article/details/143441012 |
【二】深入理解spring的依赖注入和属性填充 | https://zhenghuisheng.blog.csdn.net/article/details/143854482 |
【三】精通spring的aop的底层原理和源码实现 | https://zhenghuisheng.blog.csdn.net/article/details/144012934 |
【四】spring中refresh刷新机制的流程和实现 | https://zhenghuisheng.blog.csdn.net/article/details/144118337 |
【五】spring中循环依赖的解决和底层实现 | https://zhenghuisheng.blog.csdn.net/article/details/144132213 |
【六】spring中事务的底层实现与执行流程 | https://zhenghuisheng.blog.csdn.net/article/details/144178500 |
【七】spring中事物传播机制的流程和原理 | https://zhenghuisheng.blog.csdn.net/article/details/144178500 |
spring事物传播机制的流程和原理
- 一,深入理解spring事物传播机制
- 1,事务嵌套挂起和恢复原理
- 2,事务的传播机制
- 2.1,NEVER传播机制
- 2.2,NOT_SUPPORTED传播机制
- 2.3,REQUIRES_NEW传播机制
- 2.4,NESTED传播机制
- 3,resume恢复
- 4,总结
如需转载,请附上链接:https://blog.csdn.net/zhenghuishengq/article/details/144196237
一,深入理解spring事物传播机制
在上一篇讲解了spring事务是如何被扫描和注册到spring容器,然后spring事务底层开启事务、提交事务和回滚事务的底层中心流程。因此要了解spring事务的传播机制和源码实现,需要先熟悉一下上一篇文章,因为很多源码这块都是上一章文章的一些分支源码。
接下来这篇讲解一下spring的传播属性,也是平常时开发中,遇到的比较晦涩难懂的,比如遇到以下的情况,在加了事务的方法insert中插入数据之后,然后又调了加了事务的build方法也要进行插入数据,但是build方法中抛了异常,那么build方法回滚之后insert方法是否需要回滚呢,这就需要深入的理解一下在spring中的传播属性到底是如何实现的
@Transactional
public void insert(){//插入sqlinsert("xxx");//调用build方法build();
}@Transactional
public void build(){//插入sqlinsert("xxx");throw new Exception("xxx");
}
1,事务嵌套挂起和恢复原理
接下来需要先回到getTransaction的方法中,这个方法是开启事务流程中的一个方法,详细的可以看上一篇文章。接下来查看这个方法,内部有一个核心的suspend 事务挂起方法,但是在这个里面的参数设置的值为null,因为这个insert方法上面是开启的第一个事务,前面没有需要事务先创建,因此就没有事务需要先挂起。
接下来查看这个suspend事务被挂起的方法,首先会判断事务是否被激活,然后调用真正的挂起方法 doSuspend 方法,然后设置一些基本属性,如隔离级别、事务名称等,最后封装成一个 SuspendedResourcesHolder 对象返回
真正执行挂起的方法如下,会进行一个解绑操作,就是将原来加载到ThreadLocal的事务对应的key-value移除,如在insert中调用的build方法也加了事务,那么在执行build对应的事务时,那么就会将insert的事务先挂起
@Override
protected Object doSuspend(Object transaction) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;txObject.setConnectionHolder(null);return TransactionSynchronizationManager.unbindResource(obtainDataSource());
}
在commit方法和rollback中,会有一个resume方法,然后会有一个真正执行恢复的方法doResume进行事务恢复。在执行完build方法对应的事务之后,又会将insert方法挂起的事务给恢复,然后继续执行insert事务
@Override
protected void doResume(@Nullable Object transaction, Object suspendedResources) {ConnectionHolder conHolder = (ConnectionHolder) suspendedResources;TransactionSynchronizationManager.bindResource(obtainConnectionFactory(), conHolder);
}
那么通过挂起和恢复两个方法实现事务间的传播机制,那么关于上面的insert事务和build事务的执行流程如下:
- insert事务是第一个创建的事务,所以不需要挂起和恢复前面的线程,然后创建事务数据源和Connection连接,加入到ThreadLocal变量副本中,随后开启insert对应的事务
- 第二步就是执行到build方法时,发现上面也有事务注解,然后判断发现在当前事务中已有一个创建的事务,那么就会将前面的insert事务挂起,并且移除ThreadLocal中的key/value,然后继续执行build的数据源和Connection连接,然后存入到ThreadLocal,然后继续开启事务,提交事务等操作
- 在执行完build的事务之后,然后会恢复上面的insert事务,在ThreadLocal中加入原先的key/value
- 最后执行insert事务的提交操作或者回滚操作
2,事务的传播机制
在执行build方法时,在开启事务调用getTransaction方法时,会判断是不是已经存在事务对象
如果已经有了事务对象,那么就会执行 handleExistingTransaction 方法,后文分析的这些传播进制都是这个方法里面的,如never、not_supported、requires_new、nested等传播机制
private TransactionStatus handleExistingTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled)throws TransactionException {...
}
2.1,NEVER传播机制
NEVER类型的设置参数如下
@Transactional(propagation = Propagation.NEVER)
如果传播的事务类型是 PROPAGATION_NEVER 这种never数据类型的话,那么就会直接抛异常报错,因为内部不允许已经存在一个事务,因此如果propagation设置的为NEVER,那么就直接抛异常
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {throw new IllegalTransactionStateException("Existing transaction found for transaction marked with propagation 'never'");
}
2.2,NOT_SUPPORTED传播机制
NOT_SUPPORTED类型的设置参数如下
@Transactional(propagation = Propagation.NOT_SUPPORTED)
如果传播机制是这种not_support不支持类型,和默认的传播机制不一样,这种事务也会挂起上一个事务,然后从ThreadLocal中移除这个datasource和Connection的key-value数据源,但是不会开启一个新的事物
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {if (debugEnabled) {logger.debug("Suspending current transaction");}//挂起存在的事物Object suspendedResources = suspend(transaction);boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);//创建一个新的非事物状态(保存了上一个存在事物状态的属性)return prepareTransactionStatus(definition, null, false, newSynchronization, debugEnabled, suspendedResources);
}
在方法内部又会调用一个 prepareTransactionStatus 方法,在这个方法内部就会手动的去创建一个新事物,包括创建数据源和Connection连接这些,因此这样就保证了事物的NOT_SUPPORTED机制,最后也会产生两个事物,一个是insert 事务,一个是 build 事务。
这种底层是通过jdbcTemplate这种方式建立连接并执行事务的,顾名思义不支持事务,意思就不会用spring原生的事务去操作jdbc,而是直接手动的通过jdbcTemplate这种方式操作jdbc。
2.3,REQUIRES_NEW传播机制
REQUIRES_NEW传播机制类型参数设置如下
@Transactional(propagation = Propagation.PROPAGATION_REQUIRES_NEW)
如果传播机制是这种requires_new创建一个新事物的类型,这个就是前面讲解过的逻辑,会先将前边创建的事务先挂起,然后开启一个新事物,创建新的datasource数据源,Connection连接,最后加入到ThreadLocal变量副本中,就是上面3.1的事务的嵌套和挂起,spring默认就是使用这种传播机制
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {//挂起已经存在的事物SuspendedResourcesHolder suspendedResources = suspend(transaction);try {// 是否需要新开启同步boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);//创建一个新的事物状态(包含了挂起的事务的属性)DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);//开启新的事物doBegin(transaction, definition);//把新的事物状态设置到当前的线程变量中去prepareSynchronization(status, definition);return status;}catch (RuntimeException | Error beginEx) {resumeAfterBeginException(transaction, suspendedResources, beginEx);throw beginEx;}
}
2.4,NESTED传播机制
NESTED传播机制类型参数设置如下
@Transactional(propagation = Propagation.NESTED)
使用这种方式就是和原生的使用方式一直,就是类似于直接将build写在insert方法里面了。但是build方法本身不支持提交或者回滚,而是依赖于insert的事务。如果build本身回滚,那也不会影响insert的执行;但是如果insert方法出现了回滚,那么肯定是会回滚build方法的
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {// 是否支持保存点:非JTA事务走这个分支。AbstractPlatformTransactionManager默认是true,// JtaTransactionManager复写了该方法false,DataSourceTransactionManager没有复写,还是true,if (useSavepointForNestedTransaction()) {//开启一个新的事物DefaultTransactionStatus status =prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);// 为事物设置一个回退点// savepoint 可以在一组事务中,设置一个回滚点,点以上的不受影响,点以下的回滚。(外层影响内层, 内层不会影响外层)status.createAndHoldSavepoint();return status;}else { // JTA事务走这个分支,创建新事务boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null);doBegin(transaction, definition);prepareSynchronization(status, definition);re turn status;}
}
3,resume恢复
在一中大概讲了一些一下suspend挂起的源码和实现,接下来看一些resume事务恢复的原理。接下来直接查看提交或者回滚中的这个 cleanupAfterCompletion 方法,内部主要就是这个 resume 方法
private void cleanupAfterCompletion(DefaultTransactionStatus status) {status.setCompleted();if (status.isNewSynchronization()) {TransactionSynchronizationManager.clear();}if (status.isNewTransaction()) {doCleanupAfterCompletion(status.getTransaction());}if (status.getSuspendedResources() != null) {Object transaction = (status.hasTransaction() ? status.getTransaction() : null);resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());}
}
接下来查看resume方法,内部会执行doResume方法,用于真正的去恢复被挂起的事务。除此之外,还会设置事务的一些属性,原先在事务被挂起的时候这些事务是否激活,事务的隔离级别,名字等都设置在SuspendedResourcesHolder 对象中,因此这里直接从这里面获取相应的属性即可。
接下来查看 DataSourceTransactionManager 类中的doResume方法,
@Override
protected void doResume(@Nullable Object transaction, Object suspendedResources) {TransactionSynchronizationManager.bindResource(obtainDataSource(), suspendedResources);
}
然后查看这段绑定资源的代码,安全的将数据源和Connection绑定
最后在resume方法中,还有一个 doResumeSynchronization 方法,会将这个被挂起的事务全部的恢复
private void doResumeSynchronization(List<TransactionSynchronization> suspendedSynchronizations) {TransactionSynchronizationManager.initSynchronization();for (TransactionSynchronization synchronization : suspendedSynchronizations) {synchronization.resume();TransactionSynchronizationManager.registerSynchronization(synchronization);}
}
4,总结
spring的传播机制主要是跟事务的嵌套有关,当发生事务嵌套时,spring内部会通过挂起进而恢复的方式执行被嵌套的事务,通过设置不同的传播机制的参数,来决定走哪种传播属性。一般比较常用的传播机制就是NEVER、NOT_SUPPORTED、REQUIRES_NEW、NESTED这几种机制