Ответ
Параметр 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()));
}
}
Короче, суть: Если не хочешь неожиданных сюрпризов, когда всё откатывается кучей, или наоборот, когда что-то важное не сохранилось — думай головой, какую пропагацию ставить. А то будет "ой, а чё это у меня логи пропали?" — а это ты сам, епта, не ту кнопку нажал.