UnexpectedRollbackException – 完整的场景分析

我所知道的关于这个例外的所有内容都来自Spring的文档和一些论坛post,其中有霜冻的开发人员粘贴了大量的堆栈跟踪,没有回复。

从Spring的文档:

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

我想一劳永逸地理解

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

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

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

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

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

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

我想我们需要在这里区分“逻辑”交易范围和“实际”交易……

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

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

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

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

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

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

克林斯曼

好吧,我可以告诉你如何重现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; } } } }