Что такое propagation в транзакциях и как он работает
Что проверяют
Понимаете ли вы, что транзакция это контекст, привязанный к потоку и к соединению, а не аннотация на методе. Вопрос быстро выводит на поведение транзакционного метода внутри другого такого же, и там заученный список вариантов заканчивается.
Как отвечать
Propagation это правило, по которому метод ведёт себя, встретив уже начатую транзакцию. Задаётся атрибутом аннотации, вариантов немного:
- REQUIRED, по умолчанию: присоединиться к существующей, иначе начать свою. Физически транзакция одна, фиксация общая;
- REQUIRES_NEW: приостановить текущую, взять отдельное соединение и провести независимую транзакцию со своей фиксацией;
- NESTED: точка сохранения внутри текущей, откат возвращает к ней, но фиксация всё равно общая; работает на менеджере поверх JDBC, на распределённых обычно нет;
- SUPPORTS, NOT_SUPPORTED, MANDATORY, NEVER: выполниться внутри существующей или без неё, потребовать наличие или запретить.
Практический выбор: REQUIRED почти везде; REQUIRES_NEW для записи, которая должна пережить откат основной логики (журнал попыток, аудит, отметка о неудаче); MANDATORY для внутреннего сервиса, который не имеет смысла вне чужой транзакции.
Где спотыкаются
Вызывают соседний метод того же класса и ждут смены поведения. Аннотация работает через прокси, внутренний вызов идёт мимо него, и никакая новая транзакция не начинается. Лечится выносом в отдельный компонент.
Ловят исключение из вложенного метода и считают, что спасли основную работу. При REQUIRED своей транзакции у вложенного нет, его откат лишь помечает общую как обречённую, и внешняя фиксация падает уже на выходе. Нужен либо REQUIRES_NEW, либо точка сохранения.
Расставляют REQUIRES_NEW где попало. Новая транзакция берёт из пула ещё одно соединение, пока родительская держит своё, и при нехватке соединений приложение встаёт. Вдобавок она не видит незафиксированных изменений родителя, и код, только что сохранивший сущность, её не находит.