Кейс: исключение было проглочено внутри транзакции

Ситуация

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

@Transactional
public void handle(Event event) {
    try {
        updateFirstPart(event);
        updateSecondPart(event);
    } catch (Exception exception) {
        log.error("Processing failed", exception);
    }
}

Диагностика

Аннотация @Transactional работала. Проблема возникала внутри метода: исключение не доходило до транзакционного interceptor.

Для внешнего слоя выполнение выглядело так:

метод вернулся нормально → оснований для rollback нет

Мой вклад

Я убрал широкий try/catch с транзакционной границы и позволил исключению выйти наружу:

@Transactional
public void handle(Event event) {
    updateFirstPart(event);
    updateSecondPart(event);
}

Результат

Исключение снова стало выходить за транзакционную границу. Spring получил сигнал об ошибке и смог применить настроенные правила rollback вместо ложного успешного завершения. Дальнейшая реакция — повтор, остановка или отдельная обработка — определяется конфигурацией вызывающего механизма.

Когда catch всё-таки нужен

Если ошибка действительно обработана, это должно быть видно из кода. В остальных случаях можно:

  • повторно выбросить исключение;
  • обернуть его с сохранением cause;
  • явно пометить транзакцию rollback-only;
  • вынести компенсирующую операцию в отдельную транзакцию.

Важно учитывать правила rollback: по умолчанию Spring откатывает транзакцию для unchecked exceptions, а checked exception может потребовать настройки.

Связанные заметки