Ответ вслух Middle AOP и прокси спрашивают часто обновлено

Как работает @Transactional через прокси

Вопрос

Объясните, как Spring реализует @Transactional и почему вызов из того же класса не открывает транзакцию.

Попробуйте сами

Ответьте вслух за 2 минуты, как на собесе. Потом сверьтесь с тезисами: что прозвучало, а что нет.

Показать ответ и разбор

Коротко

При создании бина BeanPostProcessor видит @Transactional и кладёт в контекст вместо бина прокси. В Spring Boot это CGLIB-подкласс.

Что должно прозвучать в ответе

  • 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 ошибок.

Термины по теме