Есть ли более простые способы решения проблем с транзакциями кроме паттерна self-inject?
Что проверяют
Понимаете ли вы, откуда берётся потребность в self-inject: @Transactional в Spring работает через прокси, а вызов this.method() внутри того же бина прокси обходит, и аннотация на вызываемом методе молча игнорируется. Следом спрашивают, какой способ вы выберете сами и почему.
Как отвечать
Сначала коротко обозначьте проблему: внешний вызов идёт через прокси, который открывает транзакцию, внутренний идёт напрямую в объект. Поэтому REQUIRES_NEW или readOnly на внутреннем методе не срабатывают. Self-inject, то есть внедрение в бин ссылки на собственный прокси через @Lazy или ObjectProvider, это обходной путь. Есть способы проще и чище:
- вынести метод в отдельный бин: самый частый и правильный вариант. Если методу нужна своя транзакция, у него, скорее всего, своя ответственность, и отдельный сервис это подтверждает;
TransactionTemplate: программная транзакция прямо в коде, без прокси. Границы видны явно, распространение задаётsetPropagationBehavior;- поднять
@Transactionalна внешний метод: если всё и так должно идти одной транзакцией, аннотация на внутреннем методе не нужна; - режим AspectJ:
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)с вплетением при компиляции или загрузке. Транзакции работают и при самовызове, и на непубличных методах, но сборка усложняется; AopContext.currentProxy(): при@EnableAspectJAutoProxy(exposeProxy = true). Работает, но привязывает бизнес-код к Spring AOP, так что это последнее средство.
Если нужно сделать что-то после коммита (отправить письмо, событие в Kafka), лучше подойдёт @TransactionalEventListener с фазой AFTER_COMMIT, а не вложенная транзакция.
Где спотыкаются
Предлагают написать свой аспект для транзакций. Spring уже делает это сам, и собственный аспект на прокси упрётся в ту же проблему.
Вспоминают паттерн Saga. Он решает распределённые транзакции между сервисами и к самовызову внутри одного бина отношения не имеет.
Не знают, что со Spring Boot 2.6 циклические зависимости по умолчанию запрещены и self-inject через поле без @Lazy может не подняться. Кто защищает этот паттерн, должен знать его цену.