【发布时间】:2020-02-22 03:37:09
【问题描述】:
这与标题所说的不完全一样,但接近。考虑一下这些 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 创建的保存点。但是,情况似乎并非如此。
在使用调试工具时,我看到的是:
- 当
doSomeWork的MainWorkerBean启动时,会创建新事务 -
methodB启动时,事务管理器正确初始化保存点并将其交给TransactionInterceptor - 当
methodA启动时,事务管理器再次看到Propagation.REQUIRED,并再次发出对实际 JDBC 事务的干净引用,它不知道保存点
这意味着当抛出异常时,TransactionStatus::hasSavepoint返回false,这会导致整个全局事务回滚,所以恢复和进一步的步骤就像丢失一样,但我的实际代码不知道回滚(因为我已经为它写了恢复)。
目前,我不能考虑将BeanA 的事务更改为Propagation.NESTED。诚然,看起来它将允许我进行更多本地回滚,但它会过于本地化,因为据我了解,Spring 将有两个保存点,并且只回滚 BeanA 保存点,而不是 @987654336 @ 一,我愿意。
我是否还缺少其他任何东西,例如配置选项,它会使与 Propagation.REQUIRED 的内部事务认为它在保存点内运行,并回滚到保存点,而不是全部?
现在我们使用的是 Spring 4.3.24,但我已经爬过了他们的代码,无法发现任何相关的变化,所以我认为升级对我没有帮助。
【问题讨论】:
-
是的,我有一个测试可以确认这种行为 - 它还允许我使用调试器检查整个事情,这就是为什么我要分发内部的
TransactionStatus对象一个保存点,但仍然表现得好像它不在一个中,并回滚整个 tx。不,我不能更改methodA的 tx,因为它不是唯一调用它的地方,在那些地方它是正确的策略。切换到MANDATORY不会改变任何东西,在这种情况下(因为doSomeWork(),tx 将存在),但现在抛出异常会破坏其他情况。 -
当
methodA进行回滚时,我应该恢复BeanB的内部状态,但仍然应该能够提交OtherBean::doSomeWork在事后所做的任何事情。整个 tx 回滚 IFFOtherBean回滚。现在,这不是正在发生的事情 - 当BeanB回滚时,OtherBean::doSomeWork也会回滚。你说得对,在这个例子中methodA()的属性无关紧要 - 但它们在整个系统中很重要,因为methodA也可以从其他地方单独调用。
标签: java spring jdbc spring-transactions