Что такое распространение (propagation) транзакций?

Ответ

Распространение транзакций (Transaction Propagation) — это атрибут, определяющий, как должна вести себя транзакция, когда метод, отмеченный @Transactional, вызывается из другого транзакционного метода.

В Spring это управляется параметром propagation в аннотации @Transactional. Основные варианты:

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

Пример: Разница между REQUIRED и REQUIRES_NEW.

@Service
public class OrderService {
    @Transactional(propagation = Propagation.REQUIRED)
    public void placeOrder(Order order) {
        // Транзакция T1
        orderRepo.save(order);
        try {
            auditService.logEvent(order); // Вызов метода с REQUIRES_NEW
        } catch (Exception e) {
            // Откат T1 НЕ произойдет из-за отката T2
        }
    }
}

@Service
public class AuditService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logEvent(Order order) {
        // Создается НОВАЯ независимая транзакция T2
        auditRepo.save(new Event("Order placed", order.getId()));
        // Если здесь произойдет исключение, откатится только T2.
        // T1 продолжит существовать.
    }
}

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

Ответ 18+ 🔞

А, слушай, смотри, сейчас тебе про транзакции так объясню, что ты офигеешь. Представь себе, блядь, бар. Это твоё приложение. Ты заказываешь пиво и ром-колу — это два метода. Вопрос в том, как охуенно это всё оплатить.

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

В Spring за это отвечает параметр propagation в аннотации @Transactional. Смотри, какие есть варианты, ядрёна вошь:

Параметр Что делает, на человеческом
REQUIRED (стоит по умолчанию) Если уже идёт общий счёт (транзакция) — ты к нему присоединяешься. Если нет — открываешь свой, один. Самый частый сценарий, чтоб не плодить сущностей.
REQUIRES_NEW А вот это, сука, интересно. Ты такой: «Похуй на ваш общий счёт, я плачу отдельно!». Создаётся новая, независимая транзакция. Если был общий счёт — его на время приостанавливают.
SUPPORTS Пофигистский вариант. Если есть общий счёт — ок, скинусь. Если нет — буду пить в долг, без транзакции.
MANDATORY Жёсткий ультиматум. «Ребят, если нет общего счёта — я нихуя не делаю, кидаю исключение!». Требует, чтобы транзакция уже была.
NOT_SUPPORTED «Я вне политики, блядь». Работает вообще без транзакции. Если кто-то начал общий счёт — его на время заморозят.
NEVER Полная противоположность MANDATORY. «Если я вижу активный общий счёт — я в ахуе, кидаю исключение!». Работает только без транзакции.
NESTED Хитрая жопа. Это как создать «чек внутри чека» — вложенную транзакцию. Если что-то пошло не так внутри — откатится только вложенная часть, а внешний счёт может продолжиться. Но не все базы данных это умеют, ёпта.

Смотри, где собака порылась. Разница между REQUIRED и REQUIRES_NEW — это просто пиздец важно.

Допустим, ты оформляешь заказ (placeOrder). Это основная операция, транзакция T1. И параллельно нужно записать лог в аудит (logEvent). Но если аудит упадёт, заказ-то откатывать нехер! Вот тут и вырубает REQUIRES_NEW.

@Service
public class OrderService {
    @Transactional(propagation = Propagation.REQUIRED)
    public void placeOrder(Order order) {
        // Транзакция T1 — наш главный счёт за заказ
        orderRepo.save(order);
        try {
            auditService.logEvent(order); // Вызываем метод с REQUIRES_NEW
        } catch (Exception e) {
            // Если тут пиздец — откатится только T2 (аудит), а T1 (заказ) — нет!
            // Заказ сохранится, а в логах будет хуй.
        }
    }
}

@Service
public class AuditService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logEvent(Order order) {
        // Создается НОВАЯ, отдельная транзакция T2
        auditRepo.save(new Event("Order placed", order.getId()));
        // Допустим, здесь случился пиздец — Unique constraint violation, ебать.
        // Откатится только T2. T1 живёт своей жизнью, заказ в базе остался.
    }
}

А почему это, блядь, важно? Да чтобы не было вот этих вот: «ой, у меня логирование упало, и весь заказ пользователя нахуй пропал». Или наоборот: «я думал, это отдельно, а оно всё в одну кучу смешалось и откатилось». Выбираешь propagation — как выбираешь стратегию: платить вместе или порознь. И от этого зависит, кто кого в случае пиздеца потянет на дно.