异常在已经捕获后传播

时间:2011-10-25 13:32:06

标签: spring hibernate flush

我发生了最奇怪的事情,我无法弄明白为什么。描述这个的最好方法是提供一个简单的例子:

@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:Transaction       回滚,因为它已被标记为仅回滚

当我使用调试器时,我可以看到错误被正确捕获,并且ModelAndView对象内部有自定义消息。所以,我不知道为什么在捕获和处理异常之后仍然会抛出异常。有没有人知道为什么会这样?

4 个答案:

答案 0 :(得分:5)

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

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

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

你应该做的是,找出实际抛出的异常(可能是打印出e.getClass().getName()),然后将该类名设置为noRollbackForClassName值。

理由是,这种情况正在发生,因为如果在尝试delete()时抛出某个异常,则当前事务会自动标记为仅回滚,如果尝试提交,则会抛出您看到的异常。传递此方法的方法是明确声明此特定异常不应导致回滚。

答案 1 :(得分:4)

问题是因为一旦抛出异常,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。但出于上述原因,请谨慎使用。

答案 2 :(得分:1)

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

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

答案 3 :(得分:0)

将属性“globalRollbackOnParticipationFailure”添加到hibernateTransactionManager bean定义中,如下所示。

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