java UnexpectedRollbackException 覆盖了我自己的异常
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3245788/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
UnexpectedRollbackException overrides my own exception
提问by Yoni
I have the following strange scenario with spring's transaction management:
我对 spring 的事务管理有以下奇怪的场景:
I have method A which calls method B which calls method C, each of them in a different class. Methods B and C are both wrapped with transactions. Both use PROPAGATION_REQUIRED, so while spring creates two logical transactions, there is one physical transaction in the db.
我有方法 A 调用方法 B 调用方法 C,每个方法都在不同的类中。方法 B 和 C 都用事务包装。两者都使用 PROPAGATION_REQUIRED,因此当 spring 创建两个逻辑事务时,数据库中有一个物理事务。
Now, in method C I throw a RuntimeException. This sets the inner logical transaction as rollbackOnly and the physical transaction as well. In method B, I am aware of the possibility of UnexpectedRollbackException, so I don't proceed to commit normally. I catch the exception from C and I throw another RuntimeException.
现在,在 CI 方法中抛出一个 RuntimeException。这将内部逻辑事务设置为 rollbackOnly,并将物理事务设置为同样。在方法B中,我意识到了UnexpectedRollbackException的可能性,所以我没有继续正常提交。我从 C 中捕获异常并抛出另一个 RuntimeException。
I expect that the outer RuntimeException will cause a rollback to the outer transaction, However the actual behavior is this:
我希望外部 RuntimeException 会导致回滚到外部事务,但实际行为是这样的:
- The outer transaction appears to try to commit, or at least check its status, and then it throws the UnexpectedRollbackException because the physical transaction was already marked as rollbackOnly.
- Before throwing that exception, it prints to the logs another exception, stating that "Application exception overridden by commit exception". Thus, Caller A receives the UnexpectedRollbackException, not the exception that B throws.
- 外部事务似乎尝试提交,或者至少检查其状态,然后它抛出 UnexpectedRollbackException,因为物理事务已被标记为 rollbackOnly。
- 在抛出该异常之前,它会将另一个异常打印到日志中,说明“应用程序异常被提交异常覆盖”。因此,调用者 A 接收到 UnexpectedRollbackException,而不是 B 抛出的异常。
I found a workaround for it, which is to actively set the outer transaction as rollback only before throwing the exception
我找到了一种解决方法,即仅在抛出异常之前主动将外部事务设置为回滚
public ModelAndView methodB(HttpServletRequest req, HttpServletResponse resp) {
try{
other.methodC();
} catch (RuntimeException e){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
throw new RuntimeException ("outer exception");
}
return handleGetRequest(req, resp);
}
However, this workaround strongly couples the code with transactions api and I'd like to avoid this. Any suggestions?
但是,这种解决方法将代码与事务 api 强耦合在一起,我想避免这种情况。有什么建议?
p.s. both transactions are meant to rollback on runtime exceptions. I didn't define any rollbackFor exception or anything like that
ps 两个事务都旨在回滚运行时异常。我没有定义任何 rollbackFor 异常或类似的东西
回答by Yoni
I found the cause of this problem. It turns out that methodB was wrapped with a cglib-based proxy (using spring old way, pre 2.0) before being wrapped in transaction. so when I throw a RuntimeException from methodB, cglib ends up throwing an InvocationTargetException, which is actually a checked exception.
我找到了这个问题的原因。事实证明,methodB 在被包装在事务中之前是用基于 cglib 的代理(使用 spring 旧方式,2.0 之前的)包装的。所以当我从 methodB 抛出一个 RuntimeException 时,cglib 最终抛出一个 InvocationTargetException,它实际上是一个已检查的异常。
Spring's transaction manager ends up catching the checked exception and tries to commit the transaction, unaware of the nested runtime exception that methodB threw. Once I discovered this, I set up the transaction wrapper to rollback for checked exceptions as well, now it works as expected.
Spring 的事务管理器最终捕获了已检查的异常并尝试提交事务,不知道 methodB 抛出的嵌套运行时异常。一旦我发现了这一点,我就设置了事务包装器来回滚已检查的异常,现在它按预期工作。
回答by wax
You could try to set failEarlyOnGlobalRollbackOnlyflag to truein your AbstractPlatformTransactionManagerinheritor (HibernateTransactionManager, for example). Here is its description:
您可以尝试在继承者中设置failEarlyOnGlobalRollbackOnly标志(例如)。这是它的描述:trueAbstractPlatformTransactionManagerHibernateTransactionManager
Set whether to fail early in case of the transaction being globally marked as rollback-only.
设置在事务被全局标记为仅回滚的情况下是否提前失败。
Default is "false", only causing an UnexpectedRollbackException at the outermost transaction boundary. Switch this flag on to cause an UnexpectedRollbackException as early as the global rollback-only marker has been first detected, even from within an inner transaction boundary. ote that, as of Spring 2.0, the fail-early behavior for global rollback-only markers has been unified: All transaction managers will by default only cause UnexpectedRollbackException at the outermost transaction boundary. This allows, for example, to continue unit tests even after an operation failed and the transaction will never be completed. All transaction managers will only fail earlier if this flag has explicitly been set to "true".
默认值为“false”,仅在最外层事务边界导致 UnexpectedRollbackException。早在首次检测到全局仅回滚标记时,即使在内部事务边界内,打开此标志也会导致 UnexpectedRollbackException。请注意,从 Spring 2.0 开始,全局仅回滚标记的早期失败行为已经统一:默认情况下,所有事务管理器只会在最外层事务边界处引发 UnexpectedRollbackException。例如,即使在操作失败并且事务永远不会完成之后,这也允许继续单元测试。如果此标志已明确设置为“true”,则所有事务管理器只会更早失败。

