Что происходит с транзакцией при поимке исключения внутри метода с @Transactional в Java?
Что проверяют
Понимаете ли вы, что Spring узнаёт об ошибке только из исключения, вышедшего за границу метода, и что транзакцией управляет прокси вокруг бина. Заодно проверяют знание умолчаний отката и того, что происходит при вызове метода того же класса.
Как отвечать
Скажите прямо: поймали исключение внутри метода и не пробросили, значит Spring ничего не заметил и транзакция закоммитится вместе со всеми изменениями, сделанными до сбоя. Прокси видит только то, чем закончился вызов.
Дальше умолчания:
- откат происходит на
RuntimeExceptionиError; - проверяемое исключение по умолчанию отката не вызывает, это меняется через
@Transactional(rollbackFor = Exception.class); - если откатить нужно, а исключение пробрасывать нельзя, транзакцию помечают вручную:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly().
Обязательно упомяните ловушку с вложенным вызовом. Если метод позвал другой бин с @Transactional, тот по умолчанию работает в распространении REQUIRED, то есть в той же транзакции. Его исключение, даже перехваченное снаружи, уже пометило транзакцию как подлежащую откату, и на выходе вместо коммита вы получите UnexpectedRollbackException. Если часть работы должна пережить откат, её выносят в отдельный бин с REQUIRES_NEW и собственным соединением.
Где спотыкаются
Оборачивают тело метода в перехват, логируют и возвращают пустой результат, а потом ищут, откуда в базе полусохранённые данные. Логирование не отменяет коммита, и если запись должна исчезнуть, надо либо пробросить исключение, либо пометить транзакцию.
Зовут аннотированный метод через this. Самовызов идёт мимо прокси, поэтому транзакции не будет вообще, и то же касается приватных и финальных методов.
Ждут отката от кода, который в транзакции не находится. Вызов из отдельного потока, работа после возврата ответа, обработчик события, отправленный вне транзакционного контекста, живут своей жизнью, и аннотация на соседнем методе им ничего не даёт.