Расскажи про использование аннотации @Transactional в Spring.

Ответ

Аннотация @Transactional в Spring декларативно управляет транзакциями, избавляя от необходимости писать шаблонный код для их начала, коммита или отката.

Ключевые параметры:

Параметр Описание Пример значения
propagation Поведение транзакции относительно существующей. Propagation.REQUIRED (по умолчанию) — использует текущую или создаёт новую. REQUIRES_NEW — всегда создаёт новую.
isolation Уровень изоляции транзакции. Isolation.READ_COMMITTED (чаще всего) — читаются только закоммиченные данные.
timeout Таймаут транзакции в секундах. 30
readOnly Оптимизация для операций только чтения. true
rollbackFor / noRollbackFor Классы исключений для отката/игнорирования отката. rollbackFor = Exception.class

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

@Service
public class TransferService {

    @Autowired
    private AccountRepository accountRepository;

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

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

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

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

  • Работает только на public-методах.
  • Самовызов внутри класса (вызов одного @Transactional-метода из другого метода того же класса) не работает из-за ограничений AOP-проксирования. Для этого нужно использовать self-injection или AspectJ mode.
  • По умолчанию откат происходит только для RuntimeException и Error.

Ответ 18+ 🔞

А, ну это же про эту вашу магию с транзакциями в Спринге! Смотри, чтобы не писать каждый раз этот шаблонный код — открыл, закоммитил, откатил, если пиздец случился — придумали аннотацию @Transactional. Вешаешь её на метод, и фреймворк сам, как нянька, оборачивает всё это дело в транзакцию. Красота, блядь!

Но просто так повесить — это для лохов. Настоящие мужики настраивают параметры, там же их, блядь, овердохуища! Главные, чтобы не облажаться:

  • propagation — это как твоя транзакция будет вести себя, если вокруг уже кто-то другой транзакцию начал. По умолчанию REQUIRED — «использую чужую, если есть, а нет — свою заведу». А вот REQUIRES_NEW — это по-пацански: «похуй на ваши, я свою, новую, создам!».
  • isolation — уровень изоляции, чтобы не начитаться хуйни из соседних, недоделанных транзакций. Чаще всего READ_COMMITTED ставят — читаешь только то, что уже нормально закоммитили, а не промежуточный пиздец.
  • timeout — чтоб транзакция не висела, как манда с ушами, вечность. Задаёшь в секундах, и если дольше — ей пиздец, откат.
  • readOnly — если ты только читаешь из базы (не пишешь), ставь true. Это как сказать базе: «Расслабься, браток, я только посмотреть».
  • rollbackFor / noRollbackFor — вот тут самое важное, ёпта! По умолчанию откат будет только если вылетит RuntimeException или Error. А если твой метод выкинет обычное проверяемое исключение (типа Exception) — транзакция закоммитится, и ты останешься с хуем в руках. Поэтому если хочешь отката от своего кастомного исключения InsufficientFundsException — явно прописывай его в rollbackFor!

Вот смотри, как это в коде выглядит, когда всё по-взрослому:

@Service
public class TransferService {

    @Autowired
    private AccountRepository accountRepository;

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

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

        accountRepository.save(from);
        accountRepository.save(to);
        // Если тут вылетит InsufficientFundsException или что-то из DataAccessException — всё, блядь, откат! Ни одно сохранение не пройдёт.
    }
}

А теперь, внимание, ебаный подвох, на котором все горят!

  1. Эта аннотация работает только на public-методах! На приватные или протектед её вешать — бесполезное занятие, как дохлой кобыле гиппопотам.
  2. Самовызов не катит! Если ты внутри одного класса вызовешь @Transactional-метод из другого метода этого же класса — магия не сработает, прокси-обёртка не вступит в игру. Получишь пиздатый баг, а транзакции не будет. Решения: либо self-injection (заинжектить самого себя, да, звучит как бред, но работает), либо переходить на режим AspectJ.
  3. Запомни раз и навсегда: по умолчанию откат — только для RuntimeException и Error. Если не указал rollbackFor для своего Exception — готовься к неожиданностям, чувак. Волнение ебать, а терпения — ноль.

Вот и вся философия. Разобрался? Теперь иди и делай правильно, а не как полупидор!