Propagation в Spring
propagation — свойство @Transactional, которое определяет, что делать при входе в метод, если транзакция уже существует или отсутствует.
Оно отвечает не за уровень изоляции и не за правила rollback, а за связь текущего вызова с транзакционным контекстом.
Режимы
| Режим | Если транзакция есть | Если транзакции нет |
|---|---|---|
REQUIRED | присоединиться | создать новую |
REQUIRES_NEW | приостановить текущую и создать отдельную | создать новую |
SUPPORTS | присоединиться | работать без транзакции |
MANDATORY | присоединиться | выбросить исключение |
NOT_SUPPORTED | приостановить | работать без транзакции |
NEVER | выбросить исключение | работать без транзакции |
NESTED | создать вложенный участок через savepoint | создать обычную транзакцию |
NONPROPAGATED в Spring нет.
REQUIRED
Это значение по умолчанию. Несколько методов могут участвовать в одной физической транзакции:
@Transactional
public void placeOrder() {
reserve();
save();
}Если внутренний участник пометил общую транзакцию как rollback-only, внешний метод не может безопасно закоммитить её. При попытке завершения Spring может выбросить UnexpectedRollbackException.
REQUIRES_NEW
Внутренний вызов получает независимую физическую транзакцию:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void writeAuditRecord(AuditRecord record) {
repository.save(record);
}Её commit или rollback не совпадает с результатом внешней транзакции. Для этого обычно требуется ещё одно соединение с БД, пока внешнее соединение удерживается. При маленьком connection pool и большом числе параллельных запросов это может привести к ожиданию соединений.
NESTED
NESTED обычно использует savepoint внутри одной физической транзакции. Вложенный участок можно откатить до savepoint, не отменяя всю работу.
Поддержка зависит от PlatformTransactionManager, драйвера и ресурса. Если вложенные транзакции не поддерживаются, нельзя рассчитывать на автоматическое превращение режима в REQUIRED: возможен NestedTransactionNotSupportedException.
Главная ловушка: вызов метода внутри того же объекта
Обычно @Transactional применяется Spring-прокси. Вызов через this не проходит через прокси, поэтому новая propagation-настройка может не включиться:
@Service
class OrderService {
public void placeOrder() {
saveAudit(); // self-invocation
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveAudit() {
// ...
}
}Транзакционную операцию обычно выносят в другой Spring-бин либо перестраивают границу операции.
Как выбирать
REQUIRED— одна атомарная бизнес-операция;REQUIRES_NEW— действительно независимый результат, который должен фиксироваться отдельно;NESTED— частичный rollback через savepoint, когда технология это поддерживает;SUPPORTS— код может использовать транзакционный контекст, но не требует его;MANDATORY— метод имеет смысл только внутри уже начатой операции;NOT_SUPPORTEDиNEVER— осознанный запрет транзакционного выполнения.
На собеседовании
Чем REQUIRES_NEW отличается от NESTED?
REQUIRES_NEW начинает независимую физическую транзакцию и временно приостанавливает внешнюю. NESTED обычно остаётся внутри внешней физической транзакции и использует savepoint; итоговый commit всё равно зависит от внешней транзакции.