Вопрос с собеседования · Spring

Как работает @Transactional и почему вызов метода внутри того же класса (self-invocation) может не открыть транзакцию?

Middle@TransactionalSpring
Ещё спрашивают так:
  • Почему @Transactional не работает при вызове метода из того же класса?
Коротко

@Transactional реализован прокси: вызов снаружи идёт через прокси, который открывает транзакцию и вызывает метод. Вызов this.other() внутри класса идёт мимо прокси — аннотация на other не срабатывает.

  • То же для @Async, @Cacheable, @Retryable и любых аспектов
  • Решения: вынести метод в другой бин; внедрить себя (@Lazy self); TransactionTemplate; AspectJ-weaving
  • Аннотация на private-методах не работает — прокси их не перехватывает

А ещё Транзакция внешнего метода, если она есть, всё равно действует на внутренний

Подробно

Как работает. Для бина с @Transactional Spring создаёт прокси (CGLIB или JDK). Внешний вызов orderService.place() приходит в прокси; TransactionInterceptor берёт соединение, начинает транзакцию, вызывает настоящий метод, коммитит или откатывает по исключению.

Self-invocation. Внутри метода this — сам объект, а не прокси. Вызов this.saveAudit() с @Transactional(propagation = REQUIRES_NEW) выполнится в текущей транзакции (или без неё), настройки аннотации проигнорированы, ошибок не будет.

@Service
class OrderService {
    public void place(Order o) {
        save(o);            // мимо прокси — @Transactional на save не работает
    }
    @Transactional
    public void save(Order o) { ... }
}

Что делать:

  • вынести метод в отдельный бин (лучший вариант — часто это и отдельная ответственность);
  • программно: TransactionTemplate.execute(...);
  • внедрить собственный прокси (@Lazy @Autowired OrderService self) — работает, но запутывает;
  • AspectJ compile-time / load-time weaving — аспекты вплетаются в байткод.

Ещё ограничения прокси: не перехватываются private и final методы (CGLIB), а у JDK-прокси — методы не из интерфейса.

Что хотят услышать

  • Прокси как механизм работы @Transactional.
  • Почему внутренний вызов идёт мимо прокси.
  • Способы решения.
  • Другие ограничения прокси.

Частые ошибки

  • Ставить @Transactional на private-метод.
  • Ожидать REQUIRES_NEW при вызове из того же класса.

Проверьте себя

Один верный вариант. Подумайте, потом откройте ответ.

Показать варианты и ответ
  • ✗Вызов this.other() тоже идёт через прокси, но транзакция стартует только на public-методе
  • ✗@Transactional на private-методе работает так же, как на public
  • ✗Self-invocation всегда открывает новую транзакцию с propagation REQUIRES_NEW
  • ✓Транзакцию открывает прокси, а this.other() идёт мимо него; выход — другой бин

Что спросят дальше

  • Какие бывают propagation?
  • Чем отличается JDK-прокси от CGLIB?

Разбор темы

Связанные вопросы

Партнёр платформы

Разбор собеседования с sotto

После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.

Разбор собеседования