spring声明式事务,finally 中return对事务回滚的影响

finally 块中使用 return 是一个常见的编程错误,它会:

跳过正常的事务提交流程。吞掉异常,使错误处理失效

导致不可预测的事务行为

Java 中 finally 和 return 的执行机制:

  1. finally 块的基本特性

在 Java 中,finally 块有一个重要特性:几乎总是会执行,即使在 try 或 catch 块中有 return 语句。

复制代码
public int example() {
    try {
        return 1;  // 这个 return 不会立即返回
    } finally {
        return 2;  // 这个 return 会覆盖上面的 return
    }
}
// 结果返回 2,而不是 1
  1. finally 中 return 对异常处理的影响

当 finally 块中有 return 时,它会改变方法的正常执行流程:

复制代码
@Transactional(rollbackFor = Exception.class)
public String testTransaction() {
    try {
        // 执行数据库操作 A
        dao.insert(recordA);
        
        // 执行数据库操作 B - 假设这里抛出异常
        dao.insert(recordB);  // 抛出异常
        
        return "success";
    } catch (Exception e) {
        // 捕获异常
        throw e;  // 重新抛出异常,期望触发事务回滚
    } finally {
        return "finally result";  // 这个 return 会干扰事务处理
    }
}

//结果返回 finally result,而不会发生异常抛出,A操作会正常执行,B操作本身异常不会正常插入数据库

Spring 事务处理机制

  1. Spring AOP 代理的工作原理

Spring 事务是通过 AOP 代理实现的,大致流程如下:

复制代码
// Spring 生成的代理代码示意
public Object invoke(MethodInvocation invocation) throws Throwable {
    TransactionInfo txInfo = createTransactionIfNecessary();
    Object retVal;
    try {
        retVal = invocation.proceed();  // 调用实际方法
    } catch (Throwable ex) {
        // 处理异常并决定是否回滚事务
        completeTransactionAfterThrowing(txInfo, ex);
        throw ex;
    } finally {
        cleanupTransactionInfo(txInfo);
    }
    // 提交事务
    commitTransactionAfterReturning(txInfo);
    return retVal;
}
  1. finally 中 return 对事务流程的干扰

当您的方法中上述A和B这样的代码时:

执行流程会变成:

Spring AOP 代理创建事务

调用实际的 testTransaction方法

执行 try 块中的业务逻辑

假设在 "执行数据库操作 B "时抛出异常

catch 块捕获并重新抛出异常

执行 finally 块

finally 中的 return 语句直接返回结果,跳过了正常的异常处理流程

Spring AOP 代理无法完成事务回滚的正常流程

Spring 事务拦截器的行为

Spring 的事务拦截器依赖于方法的正常完成或异常抛出来决定事务的提交或回滚:

复制代码
// Spring 事务处理的核心逻辑
try {
    // 执行业务方法
    retVal = proceedWithInvocation();
} catch (Throwable ex) {
    // 如果方法抛出异常,则回滚事务
    if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
        try {
            txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
        } catch (TransactionSystemException ex2) {
            // 处理回滚异常
        }
    }
    throw ex;  // 重新抛出原异常
}
// 如果方法正常完成,则提交事务
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());

当 finally 中有 return 时,异常可能不会正常传播到这个拦截器层面,导致事务处理异常。

当然了!没有异常的情况下,事务都是可以正常插入的,异常的情况才会出现问题!

最佳实践总结:

避免在 finally 块中使用 return 语句

finally 块应该只用于资源清理工作

将 return 语句放在 try-catch 结构外部

在事务方法中特别注意控制流的处理
小Tips:

@Transactional:声明这是一个事务方法

rollbackFor = Exception.class:指定遇到 Exception 及其子类时回滚事务

默认情况下,Spring 只对 RuntimeException 和 Error 进行回滚

相关推荐
阳光九叶草LXGZXJ5 小时前
达梦数据库-报错-15-列【XXX】长度超出定义
linux·运维·数据库·sql·学习
字节渡客5 小时前
Redis键明明过期了,业务还在读到旧数据
数据库·redis·spring
OnlineProxy8 小时前
亚马逊多账号运营:如何在规避“关联封号”风险的同时实现电商规模化扩张
服务器·数据库·redis
辻弋2019 小时前
五年前的旅行视频糊成马赛克?Video2X用Real-ESRGAN逐帧重建细节,但只支持Windows、集显用户建议直接放弃
服务器·数据库·windows·游戏引擎·电脑
可乐鸡翅yeah_10 小时前
业务中 M3U8 水印相关坑,硬水印和动态水印区别
前端·网络·数据库·ffmpeg·m3u8在线
石头麻辣鱼10 小时前
Bamboo 调度系统 OceanBase 适配实战:存储过程迁移踩过的三个大坑
数据库
龙亘川11 小时前
数智驱动民政升级 精准守护民生保障
大数据·数据库·人工智能
念何架构之路11 小时前
zap扩展生态与总结
java·前端·数据库
晚安日记wanna12 小时前
MySQL 回表为什么这么慢?5 种优化手段逐个拆解
数据库·mysql·性能优化
第七页独白12 小时前
汽车零件厂如何通过 QMS 真正落地 IATF 16949——QMS软件系统:品质检验-内审稽核-8d客诉管理:全星质量管理软件系统
java·前端·数据库