Как работает @Transactional через прокси
Вопрос
Объясните, как Spring реализует @Transactional и почему вызов из того же класса не открывает транзакцию.
Попробуйте сами
Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.
Показать ответ и разбор
Коротко
Что должно прозвучать в ответе
BeanPostProcessor заменяет бин прокси
CGLIB-подкласс или JDK-прокси по интерфейсу
Прокси открывает транзакцию, вызывает target, коммитит или откатывает
Внутри target вызов через this это прямой вызов, прокси его не видит
Решения: отдельный бин, внедрение себя, TransactionTemplate, AspectJ
Те же правила для @Async, @Cacheable, @PreAuthorize
Что могут спросить следом
- Почему private-метод не перехватывается?
- Что изменится, если включить AspectJ weaving?
Разбор
При создании бина BeanPostProcessor видит @Transactional и кладёт в контекст вместо бина прокси. В Spring Boot это CGLIB-подкласс. Все, кто внедряет сервис, получают прокси. При вызове метода прокси через TransactionInterceptor берёт соединение, открывает транзакцию, вызывает метод настоящего объекта и по результату коммитит или откатывает. Но когда мы уже внутри настоящего объекта и вызываем this.other(), это обычный Java-вызов, прокси в нём не участвует, поэтому аннотация на other игнорируется. Исправляю выносом метода в отдельный бин, это заодно делает дизайн чище. Альтернативы: TransactionTemplate для программной транзакции, внедрение себя через @Lazy, или AspectJ weaving, где советы вплетаются в сам класс и this-вызовы перехватываются. Те же правила касаются @Async, @Cacheable, @Retryable и @PreAuthorize.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.