С какими подводными камнями аннотации @Transactional можно столкнуться, например при вызове метода из того же класса и ограничениях на public методы?
Что проверяют
Знаете ли вы, что @Transactional это не свойство метода, а прокси вокруг бина. Почти все подводные камни выводятся из одного этого факта, и интервьюер смотрит, назовёте ли вы причину или заучили список симптомов.
Как отвечать
Сначала механизм, потом следствия. Spring оборачивает бин прокси, транзакция открывается при входе через прокси и закрывается при выходе из него.
- Вызов внутри класса.
this.save()идёт мимо прокси, аннотация молча не работает. Лечится выносом в отдельный бин, инъекцией самого себя или переходом на плетение через AspectJ, где прокси нет вовсе. - Модификаторы доступа. В режиме прокси аннотация учитывается на публичных методах, на приватных и пакетных просто игнорируется, без всякой ошибки. По той же причине не проксируются финальные классы и методы.
- Откат. По умолчанию откат идёт на
RuntimeExceptionиError, проверяемое исключение транзакцию закоммитит, если не указанrollbackFor. Перехваченное внутри исключение тоже не откатит ничего. - Помеченная на откат транзакция. Если вложенный метод упал, а внешний исключение проглотил, на коммите прилетит
UnexpectedRollbackException. - Распространение.
REQUIRES_NEWберёт из пула отдельное соединение, и при исчерпанном пуле внешняя транзакция ждёт то, что уже никогда не освободится. - Границы. После выхода из метода сессия закрыта, ленивая коллекция бросит
LazyInitializationException, а отправку события наружу стоит вешать на@TransactionalEventListenerс фазой после коммита.
Где спотыкаются
Называют самовызов, но не могут объяснить причину. Без слова «прокси» ответ звучит как примета, и следующий вопрос про приватный метод или про финальный класс остаётся без ответа.
Второе: считают, что аннотация на методе гарантирует откат при любой ошибке. Проверяемое исключение по умолчанию не откатывает, а обработанное в try не откатывает тем более, при этом часть данных уже записана.
Третье: делают внешние вызовы внутри транзакции. Сообщение в очередь или запрос в чужой сервис из метода под транзакцией означает, что получатель увидит данные, которых ещё нет в базе, а при откате событие уже уехало.