浅聊Spring的事务传播行为

Spring 中的事务传播行为

事务管理并非 Spring 首创,Spring 也借鉴了很多其他的框架,然后加以统一。

在 Spring 中,我们经常使用声明式事务,在方法或类上添加 Spring 的 @Transtional 注解,在这个注解中我们可以指定事务传播行为,这个注解也参考了 EJB 的 javax.ejb.TransactionAttribute 以及 JTA 的 javax.transaction.Transactional,这里先通过对比认识一下这三者的异同。

参数\注解 Spring @Transactional EJB @TransactionAttribute JTA @Transactional

事务管理器

transactionManager    

传播行为

propagation value value

隔离级别

isolation    

超时时间

timeout    

是否只读

readOnly    
回滚异常 rollbackFor、rollbackForClassName   rollbackOn
不回滚异常 noRollbackFor、noRollbackForClassName   dontRollbackOn

从上面的表格中可以看到,在 Spring 的 @Transactional 中都可以找到 EJB、JTA 注解中相应的参数。事实上,Spring 也对 EJB 的 @TransactionAttribute 注解及 JTA 的 @Transactional 加以了支持,在 Spring 中这三个注解都可以使用。

现在将重点转向事务传播行为,上面的三个注解都有事务传播行为,那么这三者的事务传播行为又有何异同呢?

传播行为\定义位置 Spring Propagation EJB TransactionAttributeType JTA TxType
需要事务 REQUIRED REQUIRED REQUIRED
支持事务 SUPPORTS SUPPORTS SUPPORTS
强制事务 MANDATORY MANDATORY MANDATORY
需要新事务 REQUIRES_NEW REQUIRES_NEW REQUIRES_NEW
不支持事务 NOT_SUPPORTED NOT_SUPPORTED NOT_SUPPORTED
从不使用事务 NEVER NEVER NEVER
嵌套事务 NESTED    

有没有发现一些问题?Spring 中的事务定义与 EJB、JTA 基本一致,它们名称不仅相同,事实上语义和实现也相似,而且 Spring 还增加了一个 NESTED 类型的事务传播行为。

事务传播行为主要是控制新方法执行时是否使用事务,如何处理线程中以存在的事务。下面是对 Spring 中的这7中事务传播行为的描述:

  • REQUIRED:默认的事务传播行为;需要事务:存在事务则使用已存在事务,否则创建新的事务;
  • SUPPORTS:支持已存在事务:存在事务则使用已存在事务,否则以非事务方式运行;
  • MANDATORY:强制使用事务:存在事务则使用已存在事务,否则抛出异常;
  • REQUIRES_NEW:需要新事务:存在事务则挂起已存在事务,否则创建新事务;
  • NOT_SUPPORTED:不支持事务:存在事务则挂起已存在事务,否则以非事务方式运行;
  • NEVER:从不使用事务:存在事务则抛出异常,否则以非事务方式运行;
  • NESTED:嵌套事务:存在事务则使创建保存点使用嵌套的事务,否则创建新的事务。

Spring 事务传播行为实现

Spring 事务管理的核心接口是 PlatformTransactionManager,这个接口提供了获取事务、提交事务、回滚事务的方法,子类 AbstractPlatformTransactionManager 提供模板方法,提供了事务管理的主要实现。事务传播行为用于控制事务的获取,因此查看事务获取的相关源码即可了解其实现:

public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {

    @Override
    public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
    		throws TransactionException {
    		
    	TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());    
    	// 获取事务
    	Object transaction = doGetTransaction();
    	... 省略日志相关代码    
    	if (isExistingTransaction(transaction)) {
    		// 事务是一个已存在的活动事务,根据事务隔离级别处理事务
    		return handleExistingTransaction(def, transaction, debugEnabled);
    	}
    	... 省略校验相关代码
    	if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
    		// 不存在事务,抛出异常
    		throw new IllegalTransactionStateException(
    				"No existing transaction found for transaction marked with propagation 'mandatory'");
    	} else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
    			def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
    			def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
    		SuspendedResourcesHolder suspendedResources = suspend(null);
    		... 省略日志相关代码
    		try {
    			return startTransaction(def, transaction, debugEnabled, suspendedResources);
    		} catch (RuntimeException | Error ex) {
    			resume(null, suspendedResources);
    			throw ex;
    		}
    	} else {
    		... 省略日志相关代码
    		boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
    		return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
    	}
    }
}

这段代码先获取事务对象,然后根据事务是否为线程上下文中已存在的事务执行不同的动作。线程上下文中不存在事务的逻辑已经在上面的代码中体现,存在事务的逻辑也类似,与事务传播行为的定义保持一致。我这里画了一张图便于理解:

Spring 事务传播行为

总结

Spring 定义了不同的事务传播行为,用于指定获取 TransactionStatus 时的行为,到底使用线程上下文中已有的事务对象,还是创建新的事务对象,还是抛出异常。

Spring 获取的获取事务对象关联着线程上下文中保存的资源对象,对于 JDBC 来说就是 Connection,因此只要获取到 Spring 在线程上下文中保存的资源对象就可以接入 Spring 的事务管理,Spring JDBC、MyBatis 都是这个原理。

 

参考:

 

posted @ 2022-01-10 08:37  残城碎梦  阅读(344)  评论(0编辑  收藏  举报