Кейс: исключение было проглочено внутри транзакции
Ситуация
Транзакционный обработчик события перехватывал любое исключение и записывал его в лог. В журнале была ошибка, но для 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 может потребовать настройки.