You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring中如何仅让finally块内操作避免事务回滚?

问题分析与解决方案

问题场景

假设代码如下:

@Transactional
public void test() {

    try {
        someA_insert();
        ...
        
        throw new Exception();
    }
    catch (Exception e) {
        ...
        throw new Exception();    // 1)
    }
    finally {
        //someB_insert();
        test2();
    }
}

@Transactional(value = TxType.REQUIRES_NEW)
public void test2() {
   someB_insert();
}

执行someA_insert()插入A后触发异常,触发回滚,随后在finally块中执行插入B的操作,但实际结果是A和B都被回滚了。

另外补充:如果去掉标记1)处的抛出语句,A和B都不会被回滚。

原因解释

出现该问题的核心是Spring事务的代理机制限制:

  • test2()上的REQUIRES_NEW注解要生效,必须通过Spring代理对象调用。但当前类内部直接调用test2()时,Spring无法拦截这个调用,REQUIRES_NEW相当于无效,test2()会加入到test()的同一个事务中。
  • 当test()因异常触发回滚时,整个事务(包括test2()里的操作)都会被回滚。
  • 若去掉1)处的抛出语句,test()方法最终未抛出异常,Spring会提交整个事务,因此A和B的操作都会被保留。

解决方案

要让finally块内的操作脱离原事务、不被回滚,核心是保证test2()的独立事务生效,具体可通过以下方式实现:

1. 将test2()拆分到独立Spring Bean中

把test2()放到另一个被Spring管理的类中,在当前类注入该Bean并调用:

@Service
public class CurrentService {
    @Autowired
    private IndependentTxService independentTxService;

    @Transactional
    public void test() {
        try {
            someA_insert();
            ...
            throw new Exception();
        } catch (Exception e) {
            ...
            throw new Exception();
        } finally {
            independentTxService.test2();
        }
    }
}

@Service
public class IndependentTxService {
    @Transactional(value = TxType.REQUIRES_NEW)
    public void test2() {
        someB_insert();
    }
}

跨Bean调用时Spring能拦截请求,为test2()开启独立新事务,新事务的提交/回滚与原事务完全无关,即使原事务回滚,新事务的操作仍会被提交。

2. 通过AopContext获取代理对象调用test2()

若不想拆分类,可通过AopContext获取当前类的代理对象,再调用test2():

@Transactional
public void test() {
    try {
        someA_insert();
        ...
        throw new Exception();
    } catch (Exception e) {
        ...
        throw new Exception();
    } finally {
        ((CurrentService) AopContext.currentProxy()).test2();
    }
}

@Transactional(value = TxType.REQUIRES_NEW)
public void test2() {
    someB_insert();
}

注意:需在项目启动类添加@EnableAspectJAutoProxy(exposeProxy = true),让Spring暴露代理对象。

3. 手动使用TransactionTemplate控制事务

不依赖声明式事务,直接用TransactionTemplate执行独立事务:

@Autowired
private TransactionTemplate transactionTemplate;

@Transactional
public void test() {
    try {
        someA_insert();
        ...
        throw new Exception();
    } catch (Exception e) {
        ...
        throw new Exception();
    } finally {
        transactionTemplate.execute(status -> {
            someB_insert();
            return null;
        });
    }
}

TransactionTemplate默认会开启独立新事务,执行完成后自动提交,不受原事务回滚影响。

总结

物理层面完全可以实现finally内的操作不被回滚,核心是让该操作运行在独立事务中,关键要避开Spring事务代理的内部调用陷阱,通过跨Bean调用、代理对象调用或手动事务的方式即可实现。

内容的提问来源于stack exchange,提问作者choding

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 13:35:21