用于双重嵌套事务的回滚绕过保存点

问题描述 投票:6回答:1

这与标题中所说的不完全相同,但是接近。考虑一下这些Spring bean:

@Bean
class BeanA {

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = EvilException.class)
    public void methodA() {
        /* ... some actions */
        if (condition) {
            throw new EvilException();
        }
    }
}

@Bean
class BeanB {
    @Autowired private BeanA beanA;
    final int MAX_TRIES = 3;

    @Transactional(propagation = Propagation.NESTED)
    public void methodB() {
        // prepare to call Bean A
        try {
            beanA.methodA();
            /* maybe do some more things */
        }
        catch (EvilException e) {
           /* recover from evil */
        }
    }
}

@Bean
class MainWorkerBean {
    @Autowired private BeanB beanB;
    @Autowired private OtherBean otherBean;

    @Transactional(propagation = Propagation.REQUIRED)
    public void doSomeWork() {
        beanB.methodB();
        otherBean.doSomeWork();
    }
}

重要说明:我正在使用支持保存点的JDBC事务管理器。

我期望这样做是,当引发EvilException时,BeanA的事务将回滚,通过此设置,该事务恰好是通过启动methodB创建的保存点。但是,事实并非如此。

当使用调试工具时,我看到的是这样:

  1. doSomeWorkMainWorkerBean开始时,创建新交易
  2. methodB启动时,事务管理器正确地初始化了一个保存点,并将其交给TransactionInterceptor
  3. [methodA启动时,事务管理器再次看到Propagation.REQUIRED,并再次发出对实际JDBC事务的干净引用,但不知道保存点

这意味着抛出异常时,TransactionStatus::hasSavepoint返回false,这将导致整个全局事务的回滚,因此恢复和进一步的步骤就像丢失一样好,但是我的实际代码不知道回滚(因为我已经为此写了恢复文件)。

[暂时,我不考虑将BeanA的交易更改为Propagation.NESTED。诚然,看起来它将允许我进行更多的本地回滚,但是它将变得太本地化,因为据我了解,Spring将会有两个保存点,并且只会回滚BeanA保存点,而不是BeanB保存点]一,如我所愿。

还有其他我想念的东西,例如配置选项,会使得Propagation.REQUIRED的内部事务认为它在保存点内运行,并回滚到保存点,而不是全部回退?

目前,我们正在使用Spring 4.3.24,但是我已经爬过了他们的代码,无法发现任何相关的更改,因此我认为升级不会对我有帮助。

java spring jdbc spring-transactions
1个回答
1
投票

如此故障单所描述:https://github.com/spring-projects/spring-framework/issues/11234

对于弹簧版本<5.0,在所述情况下,全局事务设置为'仅回滚'。

在此事务中,我正在处理几个任务。如果在单个任务期间发生错误,我不希望整个事务回滚,因此我将任务处理包装在另一个事务边界中,并传播PROPAGATION_NESTED。

问题出现在任务处理期间,调用以PROPAGATION_REQUIRED事务边界定义的现有服务方法。这些方法抛出的任何运行时异常都会导致将基础连接标记为仅回滚,而不是考虑当前的父事务嵌套传播。

[...]

从Spring Framework 5.0开始,嵌套事务将在回滚到保存点时解析其仅回滚状态,不再将其应用于全局事务。

在旧版本上,建议的解决方法是在这种情况下将globalRollbackOnParticipationFailure切换为false

但是,即使对于Spring5,我在重现问题时也注意到,嵌套事务可能会回滚,包括在methodB()的catch块中所做的所有事情。因此,您的恢复代码可能无法在methodB()内运行,具体取决于您的恢复情况。如果methodA()不是事务性的,那将不会发生。只是要提防的事情。

此处有更多详细信息:https://github.com/spring-projects/spring-framework/issues/8135

© www.soinside.com 2019 - 2024. All rights reserved.