В Spring, каково поведение транзакций при вызове метода b из метода a, помеченного @Transactional?

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

Ответ

Поведение зависит от того, помечен ли метод b аннотацией @Transactional и какое у него распространение (propagation).

Сценарии:

  1. Метод b НЕ помечен @Transactional:

    • Код метода b выполняется в той же транзакции, что и метод a. Аннотации нет — новых транзакций не создаётся.
  2. Метод b помечен @Transactional с propagation по умолчанию (REQUIRED):

    • Если вызов идёт через прокси (например, из другого бина), то Spring присоединит метод b к существующей транзакции метода a.
    • Если вызов идёт внутри одного класса (как в вопросе), аннотация @Transactional на b игнорируется из-за ограничений proxy-based AOP. Работает как сценарий 1.
  3. Метод b помечен @Transactional(propagation = Propagation.REQUIRES_NEW):

    • При вызове через прокси Spring приостановит текущую транзакцию метода a, создаст и запустит новую независимую транзакцию для b, а после её завершения возобновит транзакцию a.
    • При внутреннем вызове внутри класса — снова не сработает (как в п.2).

Практический пример с разными propagation:

@Service
public class TransactionService {
    @Transactional
    public void methodA() {
        // Транзакция Tx-A
        repo.save(entityA);
        try {
            // Вызов через прокси другого бина:
            otherService.methodB_REQUIRES_NEW(); // Будет Tx-B
        } catch (Exception e) {
            // Откат Tx-B НЕ повлияет на Tx-A
        }
        // Продолжение Tx-A
    }
}

@Service
class OtherService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB_REQUIRES_NEW() {
        // Новая, независимая транзакция Tx-B
        // Её откат не затронет Tx-A
    }
}

Итог: Чтобы управление транзакциями работало предсказуемо, вызовы должны идти через прокси (между разными бинами).