Для чего используется параметр propagation в аннотации @Transactional в Spring?

Ответ

Параметр propagation определяет поведение транзакции при её вызове из другого транзакционного метода. Он управляет тем, как должна вести себя новая транзакция в контексте существующей.

Основные значения:

  • REQUIRED (по умолчанию): Использует существующую транзакцию, если она есть, или создаёт новую.
  • REQUIRES_NEW: Всегда создаёт новую транзакцию, приостанавливая текущую, если она существует.
  • SUPPORTS: Выполняется в транзакции, если она есть, иначе — без транзакции.
  • NOT_SUPPORTED: Выполняет метод без транзакции, приостанавливая текущую, если она есть.
  • MANDATORY: Требует активной транзакции, иначе выбрасывает исключение.
  • NEVER: Требует отсутствия транзакции, иначе выбрасывает исключение.
  • NESTED: Создаёт вложенную транзакцию внутри существующей (работает, если БД поддерживает savepoints).

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

@Service
public class OrderService {
    @Transactional(propagation = Propagation.REQUIRED)
    public void placeOrder(Order order) {
        // Основная логика заказа
        saveOrder(order);
        // Логирование должно быть независимо от отката основной транзакции
        logAuditEvent(order);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logAuditEvent(Order order) {
        // Эта запись в лог-таблицу будет сохранена,
        // даже если основная транзакция placeOrder откатится
        auditRepository.save(new AuditEvent("Order placed", order.getId()));
    }
}

Почему это важно: Правильный выбор propagation предотвращает непредсказуемое поведение (например, нежелательный откат логирования или блокировку данных) и обеспечивает атомарность операций на нужном уровне.

Ответ 18+ 🔞

А, ну вот, подъехали к самому сокровенному, к этим вашим пропагациям! Это ж, блядь, как в семье: кто главный, кто под кем лежит, и что будет, если один наебнётся.

Смотри, есть у тебя метод, который крутится внутри транзакции. И он зовёт другой метод, который тоже с аннотацией @Transactional. Так вот, параметр propagation — это такой договорняк между ними, инструкция на случай, если их встретят в одном месте. Он решает: то ли им пить из одного стакана, то ли разойтись по углам.

Основные засады, простите, значения:

  • REQUIRED (дефолтный, он же самый частый): Классика жанра. "Братан, если у тебя уже есть транзакция — я к тебе присосучусь. Нету? Ну я тогда свою замучу". Как в хорошей компании — все в одной лодке.
  • REQUIRES_NEW: А вот это уже похуистичнее. "Отъебись от меня со своей старой транзакцией, я свою новую заведу!". Текущую транзакцию он, сука, приостанавливает, делает свои делишки в новой, а потом возвращает всё как было. Независимость, блядь, полная.
  • SUPPORTS: Пофигист. "Есть транзакция — ок, нет транзакции — тоже ок, я и так поработаю". Никаких обязательств.
  • NOT_SUPPORTED: Антисоциальный тип. "Я буду работать БЕЗ транзакции. А твою текущую, если мешает, — нахуй приостановлю". Выполняет метод в не транзакционном контексте.
  • MANDATORY: Истеричка. "Мне ОБЯЗАТЕЛЬНО нужна транзакция! Если ты меня вызвал без неё — я в истерику, в исключение!".
  • NEVER: Полная противоположность. "Я ни в коем случае не буду работать внутри транзакции! Если она есть — это повод для скандала (исключения)!".
  • NESTED: Хитрый лис. Создаёт вложенную транзакцию, точку сохранения (savepoint) внутри большой. Если вложенная облажается, можно откатить только её кусок, а не всю большую транзакцию. Но это если твоя БД такие фокусы поддерживает, а то будет "ой, всё".

Зачем этот цирк? Ну вот смотри реальную историю, где REQUIRES_NEW — просто герой.

Допустим, ты оформляешь заказ. Всё прошло, заказ сохранился, но потом, блядь, на этапе списания денег что-то пошло не так, и вся транзакция откатилась. И заказа нет, и логирования этого события нет — тишина, как в танке. Кто звонил? Что хотел? Хуй поймёшь.

Вот чтобы такого не было, логирование аудита делают в отдельной, независимой транзакции. Она сохранит запись, даже если основной заказ накрылся медным тазом.

@Service
public class OrderService {
    @Transactional(propagation = Propagation.REQUIRED) // Основная транзакция заказа
    public void placeOrder(Order order) {
        // Сохраняем сам заказ
        saveOrder(order);
        // А вот логирование пускай живёт своей жизнью
        logAuditEvent(order); // Вызываем метод с REQUIRES_NEW
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW) // Отдельная, новая транзакция!
    public void logAuditEvent(Order order) {
        // Эта запись в таблицу аудита сохранится ВНЕ ЗАВИСИМОСТИ
        // от того, откатится основная транзакция placeOrder или нет.
        auditRepository.save(new AuditEvent("Order placed", order.getId()));
    }
}

Короче, суть: Если не хочешь неожиданных сюрпризов, когда всё откатывается кучей, или наоборот, когда что-то важное не сохранилось — думай головой, какую пропагацию ставить. А то будет "ой, а чё это у меня логи пропали?" — а это ты сам, епта, не ту кнопку нажал.