年年岁岁花相似,岁岁年年人不同。

SpringBoot 事务传播行为

准备

pom 依赖,这里 ORM 框架使用 Mybatis-Plus, AOP 用于获取代理对象,因为 Spring 事务基于 AOP 实现,某些场景需要使用 当前类的代理对象,不然会导致事务失效。

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>
    </dependencies>

获取当前项目 PlatformTransactionManager 实现类包名

image-20230508213541347

开启日志打印

logging:
  level:
    org.springframework.jdbc.support: debug

以转账操作为例

  • transfer:转账
  • deduct:扣款
  • add:到账

不使用代理对象调用本类方法

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void deduct(Integer id, BigDecimal money) {
        baseMapper.deduct(id, money);
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void add(Integer id, BigDecimal money) {
        baseMapper.add(id, money);
    }

    @Override
    @Transactional(rollbackFor = NoClassDefFoundError.class, propagation = Propagation.REQUIRED)
    public void transfer(Integer from, Integer to, BigDecimal money) {
        deduct(from, money);
        add(to, money);
        throw new RuntimeException("error");
    }

日志中显示只开启了一个事务

image-20230508215629963

开启 AOP 代理

@EnableAspectJAutoProxy(exposeProxy = true)
    @Override
    @Transactional(rollbackFor = NoClassDefFoundError.class, propagation = Propagation.REQUIRED)
    public void transfer(Integer from, Integer to, BigDecimal money) {
        BalanceServiceImpl balanceService = (BalanceServiceImpl) AopContext.currentProxy();
        balanceService.deduct(from, money);
        balanceService.add(to, money);
        throw new RuntimeException("error");
    }

日志打印: 开启了3个事务

image-20230508215508786

MANDATORY

如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)

REQUIRES_NEW

创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

调用 transfer 方法,add 方法出现异常,deduct 方法与 add 方法处于不同事务,但是还是分别回滚了。

image-20230508231255516

image-20230508234122612

按道理 add 方法抛出异常导致其事务回滚;在 transfer 方法中没有捕获该 RuntimeException,会被继续抛出,但是这个 RuntimeException 并不在 rollbackFor = NoClassDefFoundError.class 的规则的范围内,为什么会回滚呢?

启用代码调试,在 TransactionAspectSupport 的 completeTransactionAfterThrowing 方法决定了出现那些异常才需要进行回滚

	protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
		if (txInfo != null && txInfo.getTransactionStatus() != null) {
			if (logger.isTraceEnabled()) {
				logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
						"] after exception: " + ex);
			}
			if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
				try {
					txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
				}
				catch (TransactionSystemException ex2) {
					logger.error("Application exception overridden by rollback exception", ex);
					ex2.initApplicationException(ex);
					throw ex2;
				}
				catch (RuntimeException | Error ex2) {
					logger.error("Application exception overridden by rollback exception", ex);
					throw ex2;
				}
			}
			else {
				// We don't roll back on this exception.
				// Will still roll back if TransactionStatus.isRollbackOnly() is true.
				try {
					txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
				}
				catch (TransactionSystemException ex2) {
					logger.error("Application exception overridden by commit exception", ex);
					ex2.initApplicationException(ex);
					throw ex2;
				}
				catch (RuntimeException | Error ex2) {
					logger.error("Application exception overridden by commit exception", ex);
					throw ex2;
				}
			}
		}
	}

然后 txInfo.transactionAttribute.rollbackOn(ex) if 判断来到 RuleBasedTransactionAttribute 的 rollbackOn 方法

image-20230508232425161

该方法中由于 RuntimeException 不符合 rollbackFor = NoClassDefFoundError.class 规则,又进一步调用了 DefaultTransactionAttribute 的 rollbackOn 方法,显然该方法会返回 true,因此最终该事务也会回滚。

image-20230508232700755

根据提示,我们可以自定义业务异常,即便发生了某个业务异常(Checked),在某些情况下,我们也不希望事务被回滚。

自定义异常 ,执行 transfer 方法后,add 方法依然回滚,transfer 所在事务被提交

public class BusinessException extends Exception{
    public BusinessException(String message) {
        super(message);
    }
}

image-20230508235050609

image-20230508235405483

REQUIRED

默认事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。也就是说:

  • 如果外部方法没有开启事务的话,Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
  • 如果外部方法开启事务并且被Propagation.REQUIRED的话,所有Propagation.REQUIRED修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务均回滚。

调用 transfer 方法,add 方法出现异常,deduct 方法与 add 方法处于同一事务,一同回滚。

image-20230508224649368

image-20230508224423585

NESTED

如果当前存在事务,就在嵌套事务内执行;如果当前没有事务,就执行与TransactionDefinition.PROPAGATION_REQUIRED相同行为。

  • 在外部方法开启事务的情况下,在内部开启一个新的事务,作为嵌套事务存在。
    • 外部事务回滚,嵌套事务也会回滚
    • 内部事务回滚,外部事务根据异常回滚规则决定是否回滚
  • 如果外部方法无事务,则单独开启一个事务,与 PROPAGATION_REQUIRED 类似。

image-20230509103525227

image-20230509103719249

SUPPORT

支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】

NOT_SUPPORTED

以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】

NEVER

以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】

总结

Spring 中的事务传播行为有:

  • MANDATORY
  • REQUIRES_NEW
  • REQUIRE
  • NESTED
  • SUPPORT
  • NOT_SUPPORTED
  • NEVER

除了 PROPAGATION_NOT_SUPPORTED 和 PROPAGATION_NEVER,其它传播行为均支持事务,其中:

如果外部方法 A() 中调用了 方法 a() ,A() 未捕获 a() 抛出的异常, 并且 a() 发生以下异常会导致 A() 事务回滚

  • RuntimeException
  • Error
  • 符合 A() 事务的 rollbackFor 规则,不在 noRollbackFor 规则

版权声明:如无特别声明,本站收集的文章归  HuaJi66/Others  所有。 如有侵权,请联系删除。

联系邮箱: [email protected]

本文标题:《 Transaction-propagation 》

本文链接:/spring/springboot/%E7%9F%A5%E8%AF%86%E7%82%B9/%E4%BA%8B%E5%8A%A1%E4%B8%8E%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB/Transaction-propagation.html