Spring 事务异常 UnexpectedRollbackException

Spring框架默认事务传播为PROPAGATION_REQUIRED,当内层事务异常时会被标记为rollback-only。如果外层事务捕获异常并继续执行,Spring会在事务提交时抛出UnexpectedRollbackException。解决方案包括正确处理异常和调整事务传播行为,例如使用PROPAGATION_NESTED实现嵌套事务。在Java中,受检异常需在编译时处理,非受检异常(如RuntimeException)通常表示程序逻辑错误。Spring对RuntimeException和Error进行回滚,但不对非运行时异常自动处理。

出现原因

Spring 框架的默认事务传播方式是 PROPAGATION_REQUIRED (内层事务加入外层事务中)

在内层事务因异常结束时, Spring 会把事务标记为 rollback-only. 这时如果外层事务 catch 捕捉了异常 e, 那么外层事务方法还会继续执行代码, 直到外层事务结束.

Spring 发现事务已经被标记为 rollback-only, 外层方法却正常执行成功, 这时 Spring 就会抛出 UnexpectedRollbackException

org.springframework.transaction.UnexpectedRollbackException:
	Transaction rolled back because it has been marked as rollback-only

解决方案

  1. 希望内层事务抛出异常时 中断程序执行, 在外层事务的 catch 代码块中继续抛出异常 e
  2. 如果希望程序正常执行完, 并且希望外层事务结束时 全部提交, 则在内层事务方法中将异常 e 捕获并处理
  3. 如果希望 内层事务回滚, 但不影响外层事务提交, 需要将内层事务的传播方式指定为 PROPAGATION_NESTED. (ps:PROPAGATION_NESTED 基于数据库 savepoint 实现的嵌套事务, 外层事务的提交和回滚能够控制嵌内层事务, 而内层事务报错时, 可以返回原始 savepoint, 外层事务可以继续提交.

Java 异常

在这里插入图片描述

  • 受检异常 checked exception vs 非受检异常 unchecked exception

  • (compile-time) Exceptions (checked) and RuntimeExceptions (unchecked)

  • common checked exceptions in Java are IOException, SQLException and ParseException

  • unchecked exception reflects some error inside the program logic like ArithmeticException

    • The RuntimeException class is the superclass of all unchecked exceptions
  • It’s a good practice to use exceptions in Java so that we can separate error-handling code from regular code

    • If a client can reasonably be expected to recover from an exception, make it a checked exception (compile)
    • If a client cannot do anything to recover from the exception, make it an unchecked exception (runtime)
  • exception vs error

    • You can, and probably should, recover from an exception
    • You can, but should not, recover from an error
  • 本质上 unchecked 和 runtime 是同义词, 但…
    在这里插入图片描述

  • 不要在方法签名中 throw RuntimeException 等 unchecked exception
    在这里插入图片描述

  • Spring 默认会对 RuntimeException 和 Error 异常进行回滚

  • Spring 没有对非运行时异常(检查时异常)进行处理,这是因为非运行时异常在编码时,是需要我们开发人员手动去进行 try/catch 进行处理的,也不允许抛出非运行时异常,比如 IOException,不然编译器编译都通不过,更别谈运行程序

  • 除了 IOException 等需要 try/catch 的异常, 还有一些不需要 try/catch 处理但是也不属于 RuntimeException 的异常, 比如: SQLException,尽管 JdbcTemplate 会将 SQLException 转为 BadSqlGrammarException (具体可以参考 translateException(String task, @Nullable String sql, SQLException ex) 方法)

手动事务

  • 编程式事务管理 (TransactionTemplate 实现)
private final TransactionTemplate transactionTemplate;
private final OrderDao orderDao;

@Autowired
public OrderService(PlatformTransactionManager transactionManager, OrderDao orderDao) {
    this.transactionTemplate = new TransactionTemplate(transactionManager);
    this.orderDao = orderDao;
}

public void createOrder(Order order) {
    transactionTemplate.execute(status -> {
        try {
            orderDao.save(order);       // 保存订单
            inventoryDao.deductStock(); // 扣减库存
            return true; // 提交事务 (正常返回, 无需处理)
        } catch (Exception e) {
            status.setRollbackOnly();   // 标记回滚
            return false;
        }
    });
}
  • PlatformTransactionManager
public void updateData() {
    TransactionDefinition def = new DefaultTransactionDefinition();
    def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    def.setTimeout(30); // 超时30秒
    TransactionStatus status = transactionManager.getTransaction(def);  // get 就是开启事务
    try {
        orderDao.save(order);                // 保存订单
        inventoryDao.deductStock();          // 扣减库存
        transactionManager.commit(status);   // 提交事务
    } catch (Exception e) {
        transactionManager.rollback(status); // 回滚事务
        throw new RuntimeException("Transaction rolled back", e);
    }
}
  • 手动回滚 (在声明式事务中手动回滚)
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值