Spring Boot 事务中 rollbackFor = Exception.class 的实现解析

我们常常在事务注解中,定义rollbackFor 为事务定义异常的类型。

@Transactional(rollbackFor = Exception.class)
public Result<Boolean> updateArticle(Long articleId, ArticleCreateRequest request)         // TODO code.
}

Spring(6.2.7)

一、实现逻辑类(RuleBasedTransactionAttribute)

1. 异常回滚入口: RuleBasedTransactionAttribute#rollbackOn

public class RuleBasedTransactionAttribute extends DefaultTransactionAttribute implements Serializable {

	@Override
	public boolean rollbackOn(Throwable ex) {
		RollbackRuleAttribute winner = null;
		int deepest = Integer.MAX_VALUE;

        // 1. 检查是否有显式配置的回滚规则
		if (this.rollbackRules != null) {
			for (RollbackRuleAttribute rule : this.rollbackRules) {
				int depth = rule.getDepth(ex);
                // 2. 检查异常是否匹配回滚规则
				if (depth >= 0 && depth < deepest) {
					deepest = depth;
					winner = rule;  // 匹配则回滚
				}
			}
		}

		// User superclass behavior (rollback on unchecked) if no rule matches.
		if (winner == null) {
            // 3. 没有匹配规则时使用默认行为
			return super.rollbackOn(ex);
		}

		return !(winner instanceof NoRollbackRuleAttribute);
	}
}

 2. 回滚规则解析: RollbackRuleAttribute#getDepth

public class RollbackRuleAttribute implements Serializable{


   public int getDepth(Throwable exception) {
        // 递归调用
		return getDepth(exception.getClass(), 0);
	}


	private int getDepth(Class<?> exceptionType, int depth) {
		if (this.exceptionType != null) {
           // 1. 检查当前异常类是否匹配规则
			if (this.exceptionType.equals(exceptionType)) {
				// Found it!
				return depth;
			}
		}
         // 2. 根据名称匹配
		else if (exceptionType.getName().contains(this.exceptionPattern)) {
			// Found it!
			return depth;
		}
		// If we've gone as far as we can go and haven't found it...
        // 3. 递归检查父类 (RuntimeException->Exception->Throwable)
		if (exceptionType == Throwable.class) {
          // 没有找到.
			return -1;
		}
         // 递归
		return getDepth(exceptionType.getSuperclass(), depth + 1);
	}
}

3. 默认回滚规则: DefaultTransactionAttribute#rollbackOn

public class DefaultTransactionAttribute extends DefaultTransactionDefinition implements TransactionAttribute {

   	@Override
	public boolean rollbackOn(Throwable ex) {
		return (ex instanceof RuntimeException || ex instanceof Error);
	}
}

4. rollback 调用流程图解

rollback 调用序列图

 

二、具体代码实现逻辑

1. 事务属性解析(注解 --->规则)

 SpringTransactionAnnotationParser#parseTransactionAnnotation

public class SpringTransactionAnnotationParser implements TransactionAnnotationParser, Serializable {

protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
		RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();

		Propagation propagation = attributes.getEnum("propagation");
		rbta.setPropagationBehavior(propagation.value());
		Isolation isolation = attributes.getEnum("isolation");
		rbta.setIsolationLevel(isolation.value());

		rbta.setTimeout(attributes.getNumber("timeout").intValue());
		String timeoutString = attributes.getString("timeoutString");
		Assert.isTrue(!StringUtils.hasText(timeoutString) || rbta.getTimeout() < 0,
				"Specify 'timeout' or 'timeoutString', not both");
		rbta.setTimeoutString(timeoutString);

		rbta.setReadOnly(attributes.getBoolean("readOnly"));
		rbta.setQualifier(attributes.getString("value"));
		rbta.setLabels(Set.of(attributes.getStringArray("label")));

		List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
         // 解析 rollbackFor 配置
		for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {
			rollbackRules.add(new RollbackRuleAttribute(rbRule));
		}
		for (String rbRule : attributes.getStringArray("rollbackForClassName")) {
			rollbackRules.add(new RollbackRuleAttribute(rbRule));
		}
        // 解析 noRollbackFor 配置
		for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {
			rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
		}
		for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {
			rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
		}
		rbta.setRollbackRules(rollbackRules);

		return rbta;
	}
}

2. 异常处理核心

 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;
				}
			}
		}
	}

3. 回滚规则匹配:递归调用,异常继承

RollbackRuleAttribute#getDepth

	private int getDepth(Class<?> exceptionType, int depth) {
		if (this.exceptionType != null) {
			if (this.exceptionType.equals(exceptionType)) {
				// Found it!
				return depth;
			}
		}
		else if (exceptionType.getName().contains(this.exceptionPattern)) {
			// Found it!
			return depth;
		}
		// If we've gone as far as we can go and haven't found it...
		if (exceptionType == Throwable.class) {
			return -1;
		}
		return getDepth(exceptionType.getSuperclass(), depth + 1);
	}

4. 实际回滚操作:

 AbstractPlatformTransactionManager#processRollback

public abstract class AbstractPlatformTransactionManager
		implements PlatformTransactionManager, ConfigurableTransactionManager, Serializable {

private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
		try {
			boolean unexpectedRollback = unexpected;
			boolean rollbackListenerInvoked = false;

			try {
				triggerBeforeCompletion(status);
                // 嵌套事务回滚处理
				if (status.hasSavepoint()) {
					if (status.isDebug()) {
						logger.debug("Rolling back transaction to savepoint");
					}
					this.transactionExecutionListeners.forEach(listener -> listener.beforeRollback(status));
					rollbackListenerInvoked = true;
					status.rollbackToHeldSavepoint();
				}
                // 新事务回滚
				else if (status.isNewTransaction()) {
					if (status.isDebug()) {
						logger.debug("Initiating transaction rollback");
					}
					this.transactionExecutionListeners.forEach(listener -> listener.beforeRollback(status));
					rollbackListenerInvoked = true;
					doRollback(status);
				}
				else {
					// Participating in larger transaction
                   // 参与现有事务
					if (status.hasTransaction()) {
						if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
							if (status.isDebug()) {
								logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
							}
							doSetRollbackOnly(status);
						}
						else {
							if (status.isDebug()) {
								logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
							}
						}
					}
					else {
						logger.debug("Should roll back transaction but cannot - no transaction available");
					}
					// Unexpected rollback only matters here if we're asked to fail early
					if (!isFailEarlyOnGlobalRollbackOnly()) {
						unexpectedRollback = false;
					}
				}
			}
			catch (RuntimeException | Error ex) {
				triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
				if (rollbackListenerInvoked) {
					this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, ex));
				}
				throw ex;
			}

			triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
			if (rollbackListenerInvoked) {
				this.transactionExecutionListeners.forEach(listener -> listener.afterRollback(status, null));
			}

			// Raise UnexpectedRollbackException if we had a global rollback-only marker
			if (unexpectedRollback) {
				throw new UnexpectedRollbackException(
						"Transaction rolled back because it has been marked as rollback-only");
			}
		}
		finally {
			cleanupAfterCompletion(status);
		}
	}
}

 

三、rollbackFor = Exception.class 的特殊处理

当配置 @Transactional(rollbackFor = Exception.class) 时:

1、规则创建

RollbackRuleAttribute rule = new RollbackRuleAttribute(Exception.class);

2、异常匹配: 

  • 任何 Exception 的子类都会匹配(深度 >= 0)
  • 包括 SQLExceptionIOException 等受检异常
  • 也包括 RuntimeException 及其子类

3、覆盖默认行为

  • 默认只回滚 RuntimeException 和 Error
  • 此配置覆盖了默认行为

4、与 noRollbackFor 交互

@Transactional( rollbackFor = Exception.class, noRollbackFor = BusinessException.class )

  • BusinessException 不会触发回滚。
  • 其他 Exception 子类都会触发回滚

 

 四、代码例子:

@Service
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private PaymentService paymentService;
    
    // 配置对所有Exception回滚
    @Transactional(rollbackFor = Exception.class)
    public void processOrder(Order order) throws PaymentException {
        // 保存订单
        orderRepository.save(order);
        
        try {
            // 支付操作(可能抛出受检异常)
            paymentService.processPayment(order);
        } catch (PaymentException ex) {
            // 记录日志但继续处理
            log.error("支付处理失败", ex);
        }
        
        // 发送通知(可能抛出IOException)
        sendOrderConfirmation(order);
    }
    
    private void sendOrderConfirmation(Order order) throws IOException {
        // 发送邮件或短信
        if (someErrorCondition) {
            throw new IOException("通知发送失败");
        }
    }
}

代码解析:

1.PaymentException 场景

  • 匹配 rollbackFor = Exception.class → 触发回滚
  • sendOrderConfirmation() 抛出 IOException
  • 支付失败 ->捕获异常 ->继续执行

2.数据库操作失败

  • orderRepository.save() 抛出 DataAccessException(RuntimeException)
  • 匹配回滚规则-> 触发回滚

 

五、实现原理绘制

 1、规则解析

  • Spring 在启动时解析 @Transactional 注解
  • 将 rollbackFor 转换为 RollbackRuleAttribute 集合

 2、异常匹配

  • 出现异常时,递归检查异常继承层次
  • 深度 ≥ 0 表示匹配成功

3、回滚规则

  • RuleBasedTransactionAttribute 整合所有规则
  • 按顺序检查直到找到匹配规则
  • 没有匹配时使用默认行为(RuntimeException|Error)

 4、实际回滚

  • 根据事务状态决定回滚方式
  • 新事务:完整回滚
  • 嵌套事务:回滚到保存点
  • 参与事务:标记为仅回滚

5、JDBC 实现

  • 最终调用 Connection.rollback()
  • 由具体事务管理器(如 DataSourceTransactionManager)实现

六、注意重要注意事项

1、Error 处理

  • rollbackFor = Exception.class 不包含 Error
  • Error 会触发回滚(默认行为)
  • 如需包含 Error:@Transactional(rollbackFor = {Exception.class, Error.class})

2、异常继承

  • Spring 5.2+ 支持接口异常匹配
  • 例如:class CustomException implements MyBusinessException
  • 配置 rollbackFor = MyBusinessException.class 会匹配

 

3、性能考虑

  • 深度递归可能影响性能
  • 避免过于宽泛的规则(如 rollbackFor = Throwable.class)(不建议 ×
  • 精确指定需要回滚的异常类型

 注意版本差异,spring 每个版本的实现都略有不同。

Spring Boot 中,当你在 **ServiceA** 的方法中添加了 `@Transactional(rollbackFor = Exception.class)`,并新增了数据后调用 **ServiceB** 的方法(同样带有 `@Transactional` 注解),是否能在 **ServiceB** 中查询到 **ServiceA** 新增的数据,取决于以下几个关键因素: --- ## ✅ **结论:可以查询到 ServiceA 新增的数据** ### ✅ **前提条件:** - 使用的是 **同一个事务管理器(DataSource)** - 数据库支持事务(如 MySQL InnoDB) - Spring事务传播行为为默认行为(即 `Propagation.REQUIRED`) --- ## ✅ **原理详解** ### 1. **Spring 事务传播机制默认为 `REQUIRED`** Spring 中默认的事务传播行为是 `Propagation.REQUIRED`,意思是: - 如果当前没有事务,就新建一个事务; - 如果已经存在一个事务,则加入该事务。 因此,当 ServiceA 调用 ServiceB 时,**ServiceB 的方法会加入 ServiceA 的事务上下文中**,共享同一个事务。 ### 2. **同一个事务中,数据库的隔离级别允许读取未提交的数据吗?** 这取决于数据库的 **事务隔离级别(isolation level)**。Spring 默认使用数据库的默认隔离级别(通常是 `READ COMMITTED`)。 | 隔离级别 | 是否允许读取未提交数据 | |----------|------------------------| | READ UNCOMMITTED | 允许(可能导致脏读)| | READ COMMITTED | 不允许(只能读已提交)| | REPEATABLE READ | 不允许 | | SERIALIZABLE | 不允许 | 在默认的 `READ COMMITTED` 隔离级别下,**ServiceB 无法读取 ServiceA 中尚未提交的修改**,但在 Spring事务管理中,**因为 ServiceA 和 ServiceB 在同一个事务中,ServiceA 的插入操作已经写入数据库(但尚未提交)**,此时 ServiceB 可以看到这些数据。 > 📌 这是因为在同一个事务中,即使未提交,数据库会允许当前事务看到自己的修改。 --- ## ✅ **示例代码** ```java @Service public class ServiceA { @Autowired private ServiceB serviceB; @Autowired private UserRepository userRepository; @Transactional(rollbackFor = Exception.class) public void methodA() { User user = new User("Alice"); userRepository.save(user); // 插入数据 serviceB.methodB(); // 调用 methodB } } ``` ```java @Service public class ServiceB { @Autowired private UserRepository userRepository; @Transactional(rollbackFor = Exception.class) public void methodB() { List<User> users = userRepository.findAll(); // 能查询到 methodA 中插入的数据 System.out.println(users); } } ``` --- ## ✅ **注意:如果 ServiceB 是新事务(Propagation.REQUIRES_NEW)** 如果你将 ServiceB 设置为: ```java @Transactional(propagation = Propagation.REQUIRES_NEW) ``` 此时 ServiceB 会开启一个**新的事务**,而 ServiceA 的事务尚未提交,所以 **ServiceB 查询不到 ServiceA 插入的数据**。 --- ## ✅ **总结** | 场景 | 能否查询到 ServiceA 插入的数据 | |------|-------------------------------| | ServiceB 使用默认传播行为(REQUIRED) | ✅ 可以 | | ServiceB 使用 `REQUIRES_NEW` | ❌ 不可以 | | ServiceB 使用不同数据源 | ❌ 不可以 | | ServiceB 使用 `NOT_SUPPORTED` 或 `NEVER` | ❌ 不可以 | --- ##
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nextera-void

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值