Исключение распространяется после уже перехваченного

#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: транзакция откатывается, поскольку она была помечена как только для отката

Когда я использую отладчик, я вижу, что ошибка правильно перехвачена и что объект ModelAndView содержит пользовательское сообщение внутри него. Итак, я понятия не имею, почему исключение все еще генерируется после того, как оно было перехвачено и обработано. Кто-нибудь понимает, почему это происходит?

Ответ №1:

В @Transactional аннотации вы можете указать, следует ли откатывать транзакцию из-за данного исключения, используя noRollbackForClassName атрибут. Вы можете сделать это примерно так.

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

Однако обратите внимание, что простое указание noRollbackForClassName = "java.lang.Exception" будет означать, что оно не будет откатываться для любого исключения (или его подклассов), следовательно, это не очень хорошая практика.

Что вам нужно сделать, так это выяснить, какое исключение на самом деле генерируется первым (может быть, путем распечатки e.getClass().getName() ), затем установите это имя класса в качестве значения noRollbackForClassName .

Разумно, это происходит потому, что, если при попытке delete() генерируется какое-либо исключение, текущая транзакция автоматически помечается как только откат, и если ее пытаются зафиксировать, будет выдано исключение, которое вы видите. Способ передать это — явно указать, что это определенное исключение не должно вызывать откат.

Ответ №2:

Проблема заключается в том, что после создания исключения Spring внутренне помечает tx как rollback-only . Это полностью отделено от обработки исключений Java. У вас есть несколько вариантов:

  • Убедитесь, что ожидаемое исключение не генерирует исключения, которые расширяются RuntimeException ; Spring откатывает tx только тогда, когда это тип RuntimeException (см. Эту Страницу, Раздел 10.5.3). Исключение HibernateException расширяет исключение RuntimeException, поэтому вы получаете маркер отката.
  • Запустите каждый tx в своей собственной транзакции, переместив транзакционный метод в свой собственный класс и аннотируя его с помощью @Transactional(propagation=Propagation.REQUIRES_NEW) . Тогда каждый вызов будет выполняться в своем собственном tx и не повлияет на общий tx.
  • Используйте noRollbackForClassName стиль, упомянутый venushka. Но используйте с осторожностью по указанной причине.

Комментарии:

1. Итак, что я могу сделать, когда исключение расширяет RuntimeException?

Ответ №3:

Исключение генерируется в Bar#delete и перехватывается в Foo#delete . В строке #delete есть аннотация @Transactional, которая пересекается до того, как будет перехвачено исключение. Эта внутренняя транзакция участвует во внешней транзакции, и поэтому вся транзакция помечена для отката.

Чтобы избежать этого, вы могли бы удалить аннотацию @Transactional для Bar#delete. Этот метод уже вызывается в рамках другой транзакции.

Ответ №4:

Добавьте свойство «globalRollbackOnParticipationFailure» в определение компонента HibernateTransactionManager следующим образом.

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