双重嵌套事务的回滚绕过保存点
Rollback for doubly nested transaction bypasses savepoint
和标题说的不完全一样,但是差不多。考虑这些 Spring 个豆子:
@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
。然而,情况似乎并非如此。
在使用调试工具时,我看到的是:
- 当
MainWorkerBean
的 doSomeWork
开始时,创建新事务
- 当
methodB
启动时,事务管理器正确初始化一个保存点并将其交给TransactionInterceptor
- 当
methodA
启动时,事务管理器再次看到 Propagation.REQUIRED
,并再次发出对实际 JDBC 事务的干净引用,它不知道保存点
这意味着当抛出异常时,TransactionStatus::hasSavepoint
return false
,这将导致整个全局事务回滚,因此恢复和进一步的步骤就像丢失一样,但我的实际代码不知道回滚(因为我已经为它编写了恢复)。
目前,我不能考虑将BeanA
的交易改为Propagation.NESTED
。不可否认,看起来它会让我有更多的本地回滚,但它太本地化了,因为据我了解,Spring 然后将有两个保存点,并且只回滚 BeanA
保存点,不是我想要的 BeanB
一个。
是否还有其他任何我遗漏的东西,例如配置选项,它会使与 Propagation.REQUIRED
的内部事务认为它是 运行 在保存点内,并回滚到保存点,不是全部?
现在我们正在使用 Spring 4.3.24,但我已经浏览了他们的代码,无法发现任何相关更改,所以我认为升级对我没有帮助。
如错误票中所述:https://github.com/spring-projects/spring-framework/issues/11234
对于 spring 版本 < 5.0,在描述的情况下,全局事务设置为 'rollback-only'。
In this transaction I am processing several tasks. If an error should occur during a single task, I do not want the whole transaction to be rolled back, therefore I wrap the task processing in another transaction boundary with a propagation of PROPAGATION_NESTED.
The problem comes when, during task processing, calls are made to existing service methods defined with a transaction boundary of PROPAGATION_REQUIRED. Any runtime exceptions thrown from these methods cause the underlying connection to be marked as rollback-only, rather than respecting the current parent transaction nested propagation.
[...]
As of Spring Framework 5.0, nested transactions resolve their rollback-only status on a rollback to a savepoint, not applying it to the global transaction anymore.
On older versions, the recommended workaround is to switch globalRollbackOnParticipationFailure
to false
in such scenarios.
然而,即使是Spring5,我在重现问题时注意到,嵌套事务可能会回滚,包括在methodB()的catch块中所做的所有事情。因此,您的恢复代码可能无法在 methodB() 内部运行,具体取决于您的恢复情况。如果 methodA() 不是事务性的,那将不会发生。只是需要注意的地方。
可在此处找到更多详细信息:https://github.com/spring-projects/spring-framework/issues/8135
和标题说的不完全一样,但是差不多。考虑这些 Spring 个豆子:
@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
。然而,情况似乎并非如此。
在使用调试工具时,我看到的是:
- 当
MainWorkerBean
的doSomeWork
开始时,创建新事务 - 当
methodB
启动时,事务管理器正确初始化一个保存点并将其交给TransactionInterceptor
- 当
methodA
启动时,事务管理器再次看到Propagation.REQUIRED
,并再次发出对实际 JDBC 事务的干净引用,它不知道保存点
这意味着当抛出异常时,TransactionStatus::hasSavepoint
return false
,这将导致整个全局事务回滚,因此恢复和进一步的步骤就像丢失一样,但我的实际代码不知道回滚(因为我已经为它编写了恢复)。
目前,我不能考虑将BeanA
的交易改为Propagation.NESTED
。不可否认,看起来它会让我有更多的本地回滚,但它太本地化了,因为据我了解,Spring 然后将有两个保存点,并且只回滚 BeanA
保存点,不是我想要的 BeanB
一个。
是否还有其他任何我遗漏的东西,例如配置选项,它会使与 Propagation.REQUIRED
的内部事务认为它是 运行 在保存点内,并回滚到保存点,不是全部?
现在我们正在使用 Spring 4.3.24,但我已经浏览了他们的代码,无法发现任何相关更改,所以我认为升级对我没有帮助。
如错误票中所述:https://github.com/spring-projects/spring-framework/issues/11234
对于 spring 版本 < 5.0,在描述的情况下,全局事务设置为 'rollback-only'。
In this transaction I am processing several tasks. If an error should occur during a single task, I do not want the whole transaction to be rolled back, therefore I wrap the task processing in another transaction boundary with a propagation of PROPAGATION_NESTED.
The problem comes when, during task processing, calls are made to existing service methods defined with a transaction boundary of PROPAGATION_REQUIRED. Any runtime exceptions thrown from these methods cause the underlying connection to be marked as rollback-only, rather than respecting the current parent transaction nested propagation.
[...]
As of Spring Framework 5.0, nested transactions resolve their rollback-only status on a rollback to a savepoint, not applying it to the global transaction anymore.
On older versions, the recommended workaround is to switch
globalRollbackOnParticipationFailure
tofalse
in such scenarios.
然而,即使是Spring5,我在重现问题时注意到,嵌套事务可能会回滚,包括在methodB()的catch块中所做的所有事情。因此,您的恢复代码可能无法在 methodB() 内部运行,具体取决于您的恢复情况。如果 methodA() 不是事务性的,那将不会发生。只是需要注意的地方。
可在此处找到更多详细信息:https://github.com/spring-projects/spring-framework/issues/8135