【问题标题】:Exception propagates after already caught异常在已经捕获后传播
【发布时间】:2011-12-14 23:01:33
【问题描述】:

我遇到了最奇怪的事情,我不知道为什么。描述这一点的最佳方式是提供一个简单的示例:

@Service
@Transactional
public class Foo{
    public ModelAndView delete(@ModelAttribute("abc") Long id) {
        ModelAndView mav = new ModelAndView();
        try {
            getDaoService().delete(id); //Calls Bar.delete()
        } catch (final Exception e) {
            // Add a custom error message to the mav for the user to see
            mav.getModelMap().addAttribute(blah, blah);
        }
        return mav;
    }
}

@Service
@Transactional
public class Bar {
    public void delete(final E entity) throws HibernateException {
        if (null != entity) {
            try {
                sessionFactory.getCurrentSession().delete(entity);
            } finally {
                sessionFactory.getCurrentSession().flush();
            }
        }
    }
}

在这种特殊情况下,我试图删除一个违反约束的对象 (ORA-02292)。我希望删除因此而失败。当删除失败时,我希望向用户显示适当的自定义消息。

无法向用户显示自定义消息,调用失败并在屏幕上显示以下内容:

org.springframework.transaction.UnexpectedRollbackException:事务 回滚,因为它已被标记为仅回滚

当我使用调试器时,我可以看到错误被适当地捕获,并且 ModelAndView 对象中包含自定义消息。所以,我不知道为什么在捕获和处理后仍然抛出异常。有谁知道为什么会发生这种情况?

【问题讨论】:

    标签: spring hibernate flush


    【解决方案1】:

    @Transactional 注释上,您可以使用noRollbackForClassName 属性声明是否由于给定异常而回滚事务。你可以这样做。

    @Service
    @Transactional(noRollbackForClassName = "java.lang.Exception")
    public class YourClass {
        ...
    }
    

    但是,请注意,只说 noRollbackForClassName = "java.lang.Exception" 意味着它不会回滚任何异常(或其子类),因此它不是一个好习惯。

    你应该做的是,首先弄清楚实际抛出了什么异常(可能是通过打印出e.getClass().getName()),然后将该类名设置为 noRollbackForClassName 值。

    这是明智的,这是因为如果在尝试 delete() 时抛出一些异常,当前事务会自动标记为仅回滚,如果尝试提交,则会抛出您看到的异常。通过 this 的方法是明确声明此特定异常不应导致回滚。

    【讨论】:

      【解决方案2】:

      异常在 Bar#delete 中被抛出,并在 Foo#delete 中被捕获。 Bar#delete 上有一个 @Transactional 注释,它在捕获异常之前被交叉。这个内部事务正在参与外部事务,因此整个事务都被标记为回滚。

      为避免这种情况,您可以删除 Bar#delete 的 @Transactional 注释。此方法已在其他事务的范围内调用。

      【讨论】:

        【解决方案3】:

        问题是因为一旦抛出异常,Spring 在内部将 tx 标记为仅回滚。这与 Java 异常处理完全分开。您有多种选择:

        • 确保您预期的异常不会引发扩展 RuntimeException 的异常; Spring 仅在类型为 RuntimeExceptionsee this page,第 10.5.3 节)时回滚 tx。 HibernateException extends RuntimeException,这就是您获得回滚标记的原因。
        • 通过将事务方法移至其自己的类并使用@Transactional(propagation=Propagation.REQUIRES_NEW) 对其进行注释,在其自己的事务中运行每个 tx。然后每个调用将在自己的 tx 中运行,不会影响整体 tx。
        • 使用提到的noRollbackForClassName 风格的venushka。但出于上述原因,请谨慎使用。

        【讨论】:

        • 那么当异常扩展RuntimeException时我该怎么办?
        【解决方案4】:

        向 hibernateTransactionManager bean 定义添加属性 "globalRollbackOnParticipationFailure",如下所示。

        <bean id="hibernateTransactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
            <property name="sessionFactory" ref="hibernateSessionFactory" />
            **<property name="globalRollbackOnParticipationFailure" value="false" />**
        </bean>
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-08-26
          • 1970-01-01
          • 2020-10-26
          • 1970-01-01
          • 2018-12-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多