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

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

Ответ

Аннотация @Transactional в Spring предоставляет декларативное управление транзакциями на основе AOP (Aspect-Oriented Programming). Она избавляет разработчика от написания шаблонного кода для открытия, коммита и отката транзакций.

Ключевые атрибуты и их поведение:

  • propagation (распространение): Определяет, как должна вести себя транзакция при наличии уже существующей.
    • REQUIRED (по умолчанию): Использует текущую транзакцию или создаёт новую.
    • REQUIRES_NEW: Всегда создаёт новую транзакцию, приостанавливая текущую.
    • NESTED: Создаёт вложенную транзакцию внутри текущей.
  • isolation (изоляция): Определяет уровень изоляции транзакции (например, READ_COMMITTED).
  • readOnly: Указывает, что транзакция только для чтения (может оптимизировать работу).
  • timeout: Время (в секундах), после которого транзакция будет откатана.
  • rollbackFor / noRollbackFor: Указывает, для каких исключений выполнять или не выполнять откат.

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

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

    @Transactional(propagation = Propagation.REQUIRED,
                   isolation = Isolation.READ_COMMITTED,
                   rollbackFor = {InsufficientFundsException.class, DataAccessException.class})
    public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
        Account from = repository.findById(fromId).orElseThrow();
        Account to = repository.findById(toId).orElseThrow();

        from.debit(amount); // Списание
        to.credit(amount);  // Зачисление

        repository.save(from);
        repository.save(to);
        // При любом unchecked исключении или указанном в rollbackFor произойдёт автоматический rollback.
    }
}

Важные нюансы:

  • Проксирование: @Transactional работает через динамические прокси (или AspectJ), поэтому вызов метода @Transactional изнутри того же класса (например, через this.internalMethod()) не будет перехвачен прокси, и транзакция не откроется. Нужно использовать инъекцию самого бина или @Transactional на вызываемом публичном методе.
  • Тестирование: В интеграционных тестах можно использовать @Transactional для автоматического отката изменений после каждого теста.
  • Менеджеры транзакций: Для работы требуется правильно настроенный PlatformTransactionManager (например, DataSourceTransactionManager для JDBC).