Как Spring обрабатывает вызов одного @Transactional метода из другого?

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

Ответ

Поведение зависит от атрибута propagation транзакции внутреннего метода. Spring управляет транзакциями через AOP-прокси, поэтому вызовы должны идти через этот прокси.

Ключевые сценарии распространения (Propagation):

Propagation Поведение внутреннего метода
REQUIRED (по умолчанию) Присоединяется к существующей транзакции внешнего метода. Откат во внешнем методе приведет к откату всей работы, включая внутренний метод.
REQUIRES_NEW Приостанавливает текущую транзакцию и создает новую, независимую. Откат новой транзакции не затронет внешнюю, и наоборот.
NESTED Создает вложенную транзакцию (точка сохранения/savepoint) внутри внешней. Откат вложенной транзакции не приводит к откату внешней.
NOT_SUPPORTED Приостанавливает текущую транзакцию и выполняется без транзакции.
NEVER Выбросит исключение, если вызван внутри существующей транзакции.

Пример с REQUIRES_NEW:

@Service
public class MyService {
    @Transactional
    public void outerMethod() {
        // Работа в транзакции Tx1
        innerMethod(); // Будет выполнена в НОВОЙ транзакции Tx2
        // Продолжение работы в Tx1. Откат Tx1 не откатит Tx2.
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void innerMethod() {
        // Независимая работа в транзакции Tx2
    }
}

Критически важное ограничение (Self-Invocation): Если метод outerMethod вызывает innerMethod напрямую через this.innerMethod(), вызов минует Spring-прокси, и аннотации @Transactional внутреннего метода игнорируются. Для корректной работы вызов должен идти через прокси, например, из другого бина или через внедренную самоссылку (@Autowired).