java UnexpectedRollbackException - 一个完整的场景分析

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2007097/
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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 18:54:02  来源:igfitidea点击:

UnexpectedRollbackException - a full scenario analysis

javahibernatespringexception

提问by Eran Medan

All I know about this exception is from Spring's documentationand some forum posts with frostrated developers pasting huge stack traces, and no replies.

我所知道的关于这个异常的所有信息来自 Spring 的文档和一些论坛帖子,这些帖子被冻死的开发人员粘贴了大量的堆栈跟踪,但没有回复。

From Spring's documentation:

来自 Spring 的文档:

Thrown when an attempt to commit a transaction resulted in an unexpected rollback

当尝试提交事务导致意外回滚时抛出

I want to understand once and for all

我想一劳永逸地了解

  1. Exactly what causes it?

    • Where did the rollback occur? in the App Server code or in the Database?
    • Was it caused due to a specific underlying exception (e.g. something from java.sql.*)?
    • Is it related to Hibernate? Is it related to Spring Transaction Manager (non JTA in my case)?
  2. How to avoid it?is there any best practice to avoid it?

  3. How to debug it?it seems to be hard to reproduce, any proven ways to troubleshoot it?
  1. 究竟是什么原因造成的?

    • 回滚发生在哪里?在 App Server 代码中还是在数据库中?
    • 它是由特定的底层异常引起的吗(例如来自 java.sql.* 的某些异常)?
    • 它与休眠有关吗?它与 Spring 事务管理器(在我的情况下为非 JTA)有关吗?
  2. 如何避免?有什么最佳做法可以避免它吗?

  3. 如何调试?似乎很难重现,有什么经过验证的方法可以解决它?

采纳答案by Bozho

Scroll a little more back in the log (or increase it's buffer-size) and you will see what exactly caused the exception.

在日志中再滚动一点(或增加它的缓冲区大小),您将看到究竟是什么导致了异常。

If it happens not to be there, check the getMostSpecificCause()and getRootCause()methods of UnexpectedRollbackException- they might be useful.

如果它不存在,请检查getMostSpecificCause()getRootCause()方法UnexpectedRollbackException- 它们可能有用。

回答by Eran Medan

I found this to be answering the rest of question: https://jira.springsource.org/browse/SPR-3452

我发现这是在回答其余的问题:https: //jira.springsource.org/browse/SPR-3452

I guess we need to differentiate between 'logical' transaction scopes and 'physical' transactions here...

What PROPAGATION_REQUIRED creates is a logical transaction scope for each method that it gets applied to. Each such logical transaction scope can individually decide on rollback-only status, with an outer transaction scope being logically independent from the inner transaction scope. Of course, in case of standard PROPAGATION_REQUIRED behavior, they will be mapped to the same physical transaction. So a rollback-only marker set in the inner transaction scope does affect the outer transaction's chance to actually commit. However, since the outer transaction scope did not decide on a rollback itself, the rollback (silently triggered by the inner transaction scope) comes unexpected at that level - which is why an UnexpectedRollbackException gets thrown.

PROPAGATION_REQUIRES_NEW, in contrast, uses a completely independent transaction for each affected transaction scope. In that case, the underlying physical transactions will be different and hence can commit or rollback independently, with an outer transaction not affected by an inner transaction's rollback status.

PROPAGATION_NESTED is different again in that it uses a single physical transaction with multiple savepoints that it can roll back to. Such partial rollbacks allow an inner transaction scope to trigger a rollback for its scope, with the outer transaction being able to continue the physical transaction despite some operations having been rolled back. This is typically mapped onto JDBC savepoints, so will only work with JDBC resource transactions (Spring's DataSourceTransactionManager).

To complete the discussion: UnexpectedRollbackException may also be thrown without the application ever having set a rollback-only marker itself. Instead, the transaction infrastructure may have decided that the only possible outcome is a rollback, due to constraints in the current transaction state. This is particularly relevant with XA transactions.

As I suggested above, throwing an exception at the inner transaction scope, then catching that exception at the outer scope and translating it into a silent setRollbackOnly call there should work for your scenario. A caller of the outer transaction will never see an exception then. Since you only worry about such silent rollbacks because of special requirements imposed by a caller, I would even argue that the correct architectural solution is to use exceptions within the service layer, and to translate those exceptions into silent rollbacks at the service facade level (right before returning to that special caller).

Since your problem is possibly not only about rollback exceptions, but rather about any exceptions thrown from your service layer, you could even use standard exception-driven rollbacks all the way throughout you service layer, and then catch and log such exceptions once the transaction has already completed, in some adapting service facade that translates your service layer's exceptions into UI-specific error states.

Juergen

我想我们需要在这里区分“逻辑”事务范围和“物理”事务......

PROPAGATION_REQUIRED 为应用到的每个方法创建一个逻辑事务范围。每个这样的逻辑事务范围可以单独决定仅回滚状态,外部事务范围在逻辑上独立于内部事务范围。当然,在标准 PROPAGATION_REQUIRED 行为的情况下,它们将被映射到相同的物理事务。因此,在内部事务范围内设置的仅回滚标记确实会影响外部事务实际提交的机会。然而,由于外部事务作用域本身并没有决定回滚,所以回滚(由内部事务作用域静默触发)在那个级别是出乎意料的——这就是抛出 UnexpectedRollbackException 的原因。

相反,PROPAGATION_REQUIRES_NEW 对每个受影响的事务范围使用完全独立的事务。在这种情况下,底层物理事务将不同,因此可以独立提交或回滚,外部事务不受内部事务回滚状态的影响。

PROPAGATION_NESTED 再次不同,因为它使用具有多个可以回滚到的保存点的单个物理事务。这种部分回滚允许内部事务作用域触发其作用域的回滚,而外部事务能够继续物理事务,尽管某些操作已经回滚。这通常映射到 JDBC 保存点,因此仅适用于 JDBC 资源事务(Spring 的 DataSourceTransactionManager)。

完成讨论: UnexpectedRollbackException 也可能在应用程序本身没有设置仅回滚标记的情况下抛出。相反,由于当前事务状态的限制,事务基础结构可能已经决定唯一可能的结果是回滚。这与 XA 事务尤其相关。

正如我上面所建议的,在内部事务范围抛出异常,然后在外部范围捕获该异常并将其转换为静默 setRollbackOnly 调用,应该适用于您的场景。外部事务的调用者将永远不会看到异常。由于您只担心由于调用者强加的特殊要求而导致的这种静默回滚,我什至认为正确的架构解决方案是在服务层中使用异常,并将这些异常转换为服务外观级别的静默回滚(对在返回到那个特殊的呼叫者之前)。

由于您的问题可能不仅与回滚异常有关,而且与从您的服务层抛出的任何异常有关,您甚至可以在整个服务层中使用标准的异常驱动回滚,然后在事务发生后捕获并记录此类异常已经完成,在一些适配的服务外观中,将您的服务层的异常转换为特定于 UI 的错误状态。

于尔根

回答by Nandkumar Tekale

Well I can tell you how to reproduce the UnexpectedRollbackException. I was working on my project, and I got this UnexpectedRollbackException in following situation. I am having controller, service and dao layers in my project. What I did is in my service layer class,

好吧,我可以告诉您如何重现 UnexpectedRollbackException。我正在处理我的项目,在以下情况下我得到了这个 UnexpectedRollbackException。我的项目中有控制器、服务和 dao 层。我所做的是在我的服务层类中,

class SomeServiceClass {
    void outerTransaction() {
        // some lines of code
        innerTransaction();
        //AbstractPlatformTransactionManager tries to commit but UnexpectedRollbackException is thrown, not here but in controller layer class that uses SomeServiceClass.
    }

    void innerTransaction() {
        try {
            // someDaoMethod or someDaoOperation that throws exception
        } catch(Exception e) {
            // when exception is caught, transaction is rolled back, outer transaction does not know about it.
            // I got this point where inner transaction gets rolled back when I set HibernateTransactionManager.setFailEarlyOnGlobalRollbackOnly(true)
            // FYI : use of following second dao access is wrong, 
            try {
                // again some dao operation
            } catch(Exception e1) {
                throw e2;
            }
        }
    }
}