Как Spring обрабатывает вложенные транзакции внутри одного класса?

«Как Spring обрабатывает вложенные транзакции внутри одного класса?» — вопрос из категории Spring, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Spring управляет вложенными транзакциями через механизм распространения транзакций (Propagation Behavior), определяемый аннотацией @Transactional. Ключевые аспекты:

Основные уровни распространения для вложенных вызовов:

  • REQUIRED (по умолчанию): Если транзакция существует, метод присоединится к ней. Если нет — создаст новую. Внутренний и внешний метод работают в одной физической транзакции.
  • REQUIRES_NEW: Всегда создает новую, независимую транзакцию, приостанавливая существующую (если есть).
  • NESTED: Создает вложенную точку сохранения (savepoint) внутри существующей транзакции. Откат внутренней операции не приводит к откату всей внешней транзакции.

Почему вызов внутри одного класса может не работать? По умолчанию Spring AOP использует прокси на основе интерфейсов (или CGLIB). Вызов метода B() из метода A() того же класса происходит минуя прокси, поэтому аннотации @Transactional на B() игнорируются.

Решение:

  1. Self-injection (Внедрение самого себя):

    @Service
    public class MyService {
        @Autowired
        private MyService self; // Внедряем проксируемый бин
    
        @Transactional
        public void methodA() {
            // ... логика A
            self.methodB(); // Вызов через прокси -> транзакция работает
        }
    
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void methodB() {
            // ... логика B в отдельной транзакции
        }
    }
  2. Использование TransactionTemplate для программного управления:

    @Service
    public class MyService {
        @Autowired
        private TransactionTemplate transactionTemplate;
    
        public void methodA() {
            // ...
            transactionTemplate.execute(status -> {
                return methodB(); // Выполняется в новой транзакции
            });
        }
    }
  3. Разделение логики на разные бины (рекомендуется).