UnexpectedRollbackException - 完整的场景分析

Era*_*dan 20 java spring hibernate exception

我所知道的关于这个异常的全部内容来自Spring的文档和一些论坛帖子,其中冻结的开发人员粘贴了大量的堆栈跟踪,并且没有回复.

从Spring的文档:

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

我想一劳永逸地理解

  1. 究竟是什么原因造成的?

    • 回滚发生在哪里?在App Server代码或数据库中?
    • 它是由于特定的底层异常引起的(例如来自java.sql.*的东西)?
    • 它与Hibernate有关吗?它与Spring Transaction Manager(在我的情况下是非JTA)有关吗?
  2. 怎么避免呢?有什么最好的做法可以避免吗?

  3. 怎么调试呢?它似乎难以重现,任何经过验证的方法来解决它?

Era*_*dan 12

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

我想我们需要在这里区分"逻辑"交易范围和"实际"交易......

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

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

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

要完成讨论:如果应用程序没有设置仅回滚标记本身,也可能抛出UnexpectedRollbackException.相反,由于当前事务状态的约束,事务基础结构可能已经确定唯一可能的结果是回滚.这与XA交易特别相关.

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

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

克林斯曼


Boz*_*zho 8

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

如果碰巧不在那里,请检查getMostSpecificCause()getRootCause()方法UnexpectedRollbackException- 它们可能有用.

  • 如果日志(缓冲区大小增加)包含确切的原因,我不会在这里问它.我想了解抛出此异常的方案.为什么我的设置相关?顺便说一下HibernateTransactionManager非常常见的Hibernate + Spring配置 (4认同)
  • 跳过事务回滚的根本原因是一个常见的错误,因为它在stacktrace中非常落后,并且有时会在其间输出sql查询。我要求您提供设置,否则我(或其他任何人)无法猜测究竟是什么导致了这种情况。无论如何,请检查我更新的答案以获取另一个提示。 (2认同)

Nan*_*ale 5

好吧,我可以告诉您如何重现 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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)