Как декларативно создать транзакцию в Spring?

Ответ

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

Пример сервиса:

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

    @Transactional
    public void transfer(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.saveAll(List.of(from, to));
    }
}

Как это работает:

  1. Перед вызовом метода помеченного @Transactional, Spring открывает транзакцию (если еще не активна).
  2. При успешном завершении метода транзакция фиксируется (commit).
  3. При выбрасывании непроверяемого исключения (RuntimeException или его подкласса) транзакция откатывается (rollback).

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

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

Требования: Для работы необходимо включить управление транзакциями в конфигурации с помощью @EnableTransactionManagement.

Ответ 18+ 🔞

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

Смотри, как это просто, ёпта:

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

    @Transactional
    public void transfer(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.saveAll(List.of(from, to));
    }
}

А работает-то оно как, блядь?

  1. Ты только заходишь в метод, а Спринг уже, сука, транзакцию открыл. Если, конечно, её ещё нет — он не дурак, второй раз открывать.
  2. Всё прошло гладко, без косяков? Отлично, транзакция коммитится, и все твои изменения летят в базу.
  3. А если вылетело какое-нибудь непроверяемое исключение, типа RuntimeException? Всё, пиздец, приехали — транзакция откатывается нахуй, как будто ничего и не было. Красота!

Ну и там ещё, блядь, параметров до овердохуища можно накрутить:

  • propagation — это когда одна транзакция уже есть, а ты лезешь со своей. Тут можно сказать "да присоединяйся к моей" (REQUIRED) или "нет, блядь, заведи свою, новую, отдельную" (REQUIRES_NEW).
  • isolation — уровень изоляции, чтоб всякие грязные чтения и прочие фантомы не мозолили глаза. READ_COMMITTED, например.
  • readOnly — скажи, что ты только читаешь, Спринг может какие-то оптимизации поделать, не дурак ведь.
  • rollbackFor / noRollbackFor — тут ты сам решаешь, от каких исключений откатываться, а от каких — нет. Хозяин-барин, блядь.

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