Как работает аннотация @Transactional в Spring?

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

Ответ

Аннотация @Transactional в Spring декларативно управляет границами транзакций для методов или классов. При вызове аннотированного метода Spring создает или присоединяется к существующей транзакции, а по завершении — фиксирует (commit) или откатывает (rollback) изменения.

Основной принцип:

  1. Перед методом: Открывается транзакция (или используется существующая).
  2. Во время метода: Все операции с БД выполняются в этой транзакции.
  3. После метода: Если метод завершился успешно (без исключения) — транзакция фиксируется. Если было выброшено исключение — транзакция откатывается.

Ключевые параметры аннотации:

  • propagation — определяет поведение при вложенных вызовах. Например:
    • REQUIRED (по умолчанию): использует текущую транзакцию или создает новую.
    • REQUIRES_NEW: всегда создает новую транзакцию, приостанавливая текущую.
  • isolation — уровень изоляции транзакции (например, READ_COMMITTED).
  • rollbackFor / noRollbackFor — указывает, для каких исключений делать или не делать откат.
  • readOnlytrue для оптимизации операций только на чтение.
  • timeout — таймаут транзакции в секундах.

Пример использования:

@Service
public class TransferService {
    @Autowired
    private AccountRepository accountRepository;

    @Transactional(rollbackFor = {InsufficientFundsException.class})
    public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
        Account from = accountRepository.findById(fromId).orElseThrow();
        Account to = accountRepository.findById(toId).orElseThrow();

        if (from.getBalance().compareTo(amount) < 0) {
            throw new InsufficientFundsException("Недостаточно средств");
        }

        from.debit(amount); // Списываем
        to.credit(amount);  // Зачисляем
        // Оба вызова save() выполняются в одной транзакции.
        // При исключении балансы не изменятся.
        accountRepository.save(from);
        accountRepository.save(to);
    }
}

Важные ограничения:

  • Работает только на public-методах из-за механизма проксирования AOP.
  • По умолчанию откатывает транзакцию только для unchecked (RuntimeException) и Error исключений. Для checked-исключений отката не будет.
  • Самовызов метода внутри одного класса (без прокси) не запустит транзакционное поведение.