Почему транзакция откатывается на RuntimeException, но не на SQLException

У меня есть Spring-управляемый метод обслуживания для управления вставками базы данных. Он содержит несколько операторов вставки.

@Transactional
public void insertObservation(ObservationWithData ob) throws SQLException 
{
    observationDao.insertObservation(ob.getObservation());
            // aop pointcut inserted here in unit test
    dataDao.insertData(ob.getData());
}

У меня есть два модульных теста, которые выдают исключение перед вызовом второй вставки. Если исключением является RuntimeException, транзакция откатывается. Если это исключение SQLException, первая вставка сохраняется.

Я сбит с толку. Может кто-нибудь сказать мне, почему транзакция не откатывается на SQLException? Может кто-нибудь предложить предложение, как справиться с этим? Я мог бы поймать SQLException и выдать RuntimeException, но это только кажется странным.

4 ответа

Решение

Это определенное поведение. Из документов:

любой RuntimeException вызывает откат, а любое проверенное исключение - нет.

Это обычное поведение для всех API транзакций Spring. По умолчанию, если RuntimeException генерируется из кода транзакции, транзакция будет откатываться. Если проверенное исключение (т.е. не RuntimeException), то транзакция не будет откатана.

Основанием для этого является то, что RuntimeException Spring обычно использует классы для обозначения неисправимых состояний ошибки.

Это поведение может быть изменено по умолчанию, если вы хотите это сделать, но то, как это сделать, зависит от того, как вы используете Spring API и как вы настроили свой менеджер транзакций.

За @Transactionalпо умолчанию откат происходит во время выполнения, только непроверенные исключения. Таким образом, ваше проверенное исключение SQLException не вызывает откат транзакции; поведение может быть настроено с rollbackFor а также noRollbackFor параметры аннотации.

@Transactional(rollbackFor = SQLException.class)
public void insertObservation(ObservationWithData ob) throws SQLException 
{
    observationDao.insertObservation(ob.getObservation());
            // aop pointcut inserted here in unit test
    dataDao.insertData(ob.getData());
}

Spring широко использует RuntimeExceptions (включая использование DataAccessExceptions для переноса SQLExceptions или исключений из ORM) для случаев, когда нет восстановления после исключения. Предполагается, что вы хотите использовать проверенные исключения для случаев, когда слой выше службы должен быть уведомлен о чем-то, но вы не хотите, чтобы транзакция вмешивалась.

Если вы используете Spring, вы также можете использовать его библиотеки jdbc-wrapping и DataAccessException-средство перевода, это уменьшит объем кода, который вам нужно поддерживать, и предоставит более значимые исключения. Также наличие сервисного уровня, выдающего специфические для реализации исключения, является неприятным запахом. Пред-Spring способ заключался в создании проверенных исключений, не зависящих от реализации, обертывающих специфичные для реализации исключения, что приводило к большой загруженности работой и раздутой базе кода. Весенний путь избегает этих проблем.

Если вы хотите знать, почему Spring решил заставить вещи работать таким образом, возможно, потому, что они используют AOP для добавления обработки транзакций. Они не могут изменить сигнатуру метода, который обертывают, поэтому проверенные исключения не являются опцией.

Если непроверенное исключение (любое исключение расширяет RunTimeException) выбрасывается из метода, имеющего аннотацию транзакций Spring, транзакция будет отменена. Если выдается проверенное исключение (IOException, SQLException и т. д.), транзакция не будет откатываться. Вы можете использовать https://projectlombok.org/features/SneakyThrows из ломбока, если вы хотите, чтобы транзакция откатила любое исключение.

Другие вопросы по тегам