Ответ
Распространение транзакций (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 — как выбираешь стратегию: платить вместе или порознь. И от этого зависит, кто кого в случае пиздеца потянет на дно.