Как Spring обрабатывает исключения в методах с аннотацией @Transactional?

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

Ответ

Поведение по умолчанию:

  • Откат транзакции для RuntimeException и Error
  • Без отката для проверяемых исключений (checked exceptions)

Настройка поведения:

// Откат для всех исключений
@Transactional(rollbackFor = Exception.class)

// Не откатывать для конкретных исключений
@Transactional(noRollbackFor = {NullPointerException.class, IllegalArgumentException.class})

// Настройка изоляции и распространения
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)

Пример с потерей данных:

@Transactional
public void transferMoney(Account from, Account to, BigDecimal amount) {
    from.withdraw(amount);  // Успешно выполняется
    // Здесь возникает RuntimeException
    to.deposit(amount);     // Не выполняется, но withdraw уже зафиксирован
}

Важные особенности:

  1. Перехват исключений: Если исключение перехватывается внутри метода, транзакция не откатывается автоматически:

    try {
    riskyOperation();
    } catch (RuntimeException e) {
    // Транзакция НЕ откатится!
    log.error("Operation failed", e);
    }
  2. Явный откат:

    @Transactional
    public void process() {
    try {
        businessLogic();
    } catch (Exception e) {
        // Принудительный откат
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        throw e;
    }
    }
  3. Self-invocation: Методы, вызываемые внутри одного класса, не попадают под управление транзакций Spring (проблема self-invocation).