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

M. *_*rov 6 java spring jdbc spring-transactions

这并不完全像标题所说的那样,但接近。考虑这些 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

我期望这样做的是,当EvilException被抛出时, 的事务BeanA被回滚,这个设置恰好是通过开始创建的保存点methodB。然而,情况似乎并非如此。

在使用调试工具时,我看到的是:

  1. doSomeWorkMainWorkerBean启动,将创建新的交易
  2. methodB启动时,事务管理器可以正确初始化一个保存点,并将其双手TransactionInterceptor
  3. methodA启动时,事务管理器看到了Propagation.REQUIRED一遍,手出一个干净的再次参考实际的JDBC事务,不具有保存点知识

这意味着当抛出异常时,TransactionStatus::hasSavepointreturnfalse会导致整个全局事务的回滚,因此恢复和进一步的步骤与丢失一样好,但我的实际代码不知道回滚(因为我已经为它)。

目前,我不能考虑将BeanA的交易更改为Propagation.NESTED。不可否认,看起来它会让我有更多的本地回滚,但它会太本地化,因为据我所知,Spring 将有两个保存点,并且只回滚BeanA保存点,而不是BeanB一个,就像我一样喜欢。

是否还有其他我遗漏的东西,例如配置选项,会使内部事务Propagation.REQUIRED认为它在保存点内运行,并回滚到保存点,而不是整个事情?

现在我们使用的是 Spring 4.3.24,但我已经爬过他们的代码并且无法发现任何相关的变化,所以我认为升级对我没有帮助。

tkr*_*use 1

如本错误单中所述:https ://github.com/spring-projects/spring-framework/issues/11234

对于 spring 版本 < 5.0,在所描述的情况下,全局事务设置为“仅回滚”。

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

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

[...]

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

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

然而,即使对于 Spring5,我在重现问题时注意到,嵌套事务可能会回滚,包括 methodB() 的 catch 块中完成的所有操作。因此,您的恢复代码可能无法在 methodB() 内工作,具体取决于您的恢复情况。如果 methodA() 不是事务性的,则不会发生这种情况。只是需要注意一些事情。

更多详细信息可以在这里找到:https://github.com/spring-projects/spring-framework/issues/8135