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 всё равно зависит от внешней транзакции.

Связанные заметки