Питання 13 · Розділ 11

Що таке Propagation в Spring

Spring реалізує propagation через AOP Proxy та TransactionInterceptor.

Мовні версії: English Russian Ukrainian

🟢 Junior Level

Propagation (поширення транзакцій) — це налаштування в Spring, яке визначає, що станеться з транзакцією, коли один транзакційний метод викликає інший транзакційний метод.

Проста аналогія: Уявіть, що ви працюєте в офісі (зовнішня транзакція). До вас підходить колега з проханням допомогти (внутрішній метод). Propagation вирішує: ви продовжите свою роботу і допоможете в рамках поточного завдання (REQUIRED), відкладете все і почнете нове завдання (REQUIRES_NEW), або відмовите, якщо у вас вже є робота (NEVER).

Простий приклад:

@Service
public class OrderService {

    @Transactional  // propagation = REQUIRED за замовчуванням
    public void createOrder(Order order) {
        orderRepo.save(order);
        auditService.logOrder(order);  // викликає інший @Transactional метод
    }
}

@Service
public class AuditService {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logOrder(Order order) {
        auditRepo.save(new AuditEntry(order));  // завжди зберігається, навіть якщо createOrder впаде
    }
}

7 типів Propagation в Spring:

Тип Якщо є транзакція Якщо немає транзакції
REQUIRED (за замовчуванням) Використовує поточну Створює нову
REQUIRES_NEW Призупиняє поточну, створює нову Створює нову
NESTED Створює вкладену (savepoint) Створює нову
MANDATORY Використовує поточну Кидає виняток
SUPPORTS Використовує поточну Виконується без транзакції
NOT_SUPPORTED Призупиняє поточну Виконується без транзакції
NEVER Кидає виняток Виконується без транзакції

Коли використовувати:

  • REQUIRED — в 90% випадків (стандартна транзакційна логіка)
  • REQUIRES_NEW — для аудиту, логування, незалежних операцій
  • NESTED — для часткового відкоту вкладених операцій

🟡 Middle Level

Як це працює всередині

Spring реалізує propagation через AOP Proxy та TransactionInterceptor.

AOP (Aspect-Oriented Programming) Proxy — Spring створює обгортку навколо біна, яка перехоплює виклики методів і додає cross-cutting логіку (транзакції, логування).

Виклик: serviceA.methodA() → proxy → TransactionInterceptor

1. Interceptor перевіряє @Transactional(propagation = ...) на методі
2. Звертається до PlatformTransactionManager:

   REQUIRED:
     - Якщо транзакція вже є → приєднується (join)
     - Якщо немає → створює нову

   REQUIRES_NEW:
     - Завжди призупиняє поточну (suspend)
     - Створює нову фізичну транзакцію
     - Після завершення — відновлює попередню

   NESTED:
     - Якщо транзакція є → створює JDBC Savepoint
     - Якщо немає → створює нову транзакцію

3. Виконується цільовий метод
4. При успіху → commit (або нічого, якщо join)
5. При винятку → rollback (або rollback to savepoint для NESTED)

Ключовий момент: Propagation працює тільки при виклику через Spring Proxy (за замовчуванням). Виклик this.method() всередині того ж класу bypass-ить проксі — TransactionInterceptor не спрацьовує. При AspectJ mode weaving виклики через this також перехоплюються.

Практичне застосування

Сценарій 1: Аудит з REQUIRES_NEW

@Service
public class PaymentService {

    private final AuditService auditService;

    @Transactional
    public void processPayment(Payment payment) {
        paymentRepo.save(payment);

        try {
            auditService.logPayment(payment);  // REQUIRES_NEW — збережеться незалежно
        } catch (Exception e) {
            // Аудит впав, але платіж має пройти
            log.warn("Audit failed, payment still processed", e);
        }

        // Якщо тут впаде — payment відкотиться, але audit вже закомічений
    }
}

Сценарій 2: Batch processing з NESTED

@Service
public class BatchService {

    @Transactional
    public void processBatch(List<Order> orders) {
        for (Order order : orders) {
            try {
                processOrderNested(order);  // NESTED — частковий відкат
            } catch (Exception e) {
                // Тільки ця позиція відкотиться до savepoint
                log.error("Order {} failed, continuing", order.getId());
            }
        }
        // Загальний commit в кінці — всі успішні позиції
    }

    @Transactional(propagation = Propagation.NESTED)
    public void processOrderNested(Order order) {
        orderRepo.save(order);
        // Якщо виняток → rollback to savepoint
    }
}

Сценарій 3: MANDATORY для внутрішньої обробки

@Service
public class InternalOrderProcessor {

    @Transactional(propagation = Propagation.MANDATORY)
    public void validateOrder(Order order) {
        // Гарантовано викликається тільки з транзакційного контексту
        // Якщо викличуть напряму — виняток
        if (order.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new InvalidOrderException();
        }
    }
}

Типові помилки

Помилка Наслідок Рішення
Виклик @Transactional методу через this Propagation ігнорується, транзакція не створюється Викликати через інший бін або використати @Autowired private SelfType self
REQUIRED + внутрішній виняток Вся транзакція (включаючи зовнішній метод) помічається rollback-only Обгорнути внутрішній виклик в try-catch або використати REQUIRES_NEW
REQUIRES_NEW для пов’язаних даних Незалежний commit може створити неузгоджені дані Використовувати REQUIRED для логічно пов’язаних операцій
NESTED з Hibernate Hibernate L1 cache не синхронізується з savepoint rollback Використовувати REQUIRED + ручну обробку помилок або JPA 3.1+
PROPAGATION_NOT_SUPPORTED всередині транзакції Призупинка транзакції, втрата контексту Переконатися, що операція дійсно не вимагає транзакції

Порівняння: REQUIRED vs REQUIRES_NEW vs NESTED

Характеристика REQUIRED REQUIRES_NEW NESTED
Фізичних транзакцій 1 2+ 1 (з savepoints)
Призупинка зовнішньої tx Ні Так Ні
Незалежний commit Ні Так Ні
Частковий відкат Ні Так Так (to savepoint)
Rollback зовнішньої tx Відкочує все Не впливає на внутрішню Відкочує все
Підтримка БД Всі Всі JDBC 3.0+ savepoints
Hibernate сумісність Повна Повна Обмежена

Головна практична відмінність: якщо outer tx rollback-иться, REQUIRES_NEW зберігає дані (вже закомічені), а NESTED втрачає (savepoint відкочується). Це визначає вибір: аудит = REQUIRES_NEW, partial batch = NESTED.

Коли НЕ варто змінювати propagation

  • Стандартний CRUD: REQUIRED за замовчуванням — правильний вибір
  • Прості сервіси без вкладених викликів: propagation не має значення
  • Event-driven архітектура: кожен обробник — окрема транзакція, propagation не потрібен

🔴 Senior Level

Internal Implementation: Spring Transaction Internals

TransactionInterceptor та Propagation Decision

// Спрощена логіка з Spring Framework 6.x
// org.springframework.transaction.interceptor.TransactionAspectSupport

protected Object invokeWithinTransaction(Method method, Class<?> targetClass,
                                          InvocationCallback invocation) {

    TransactionAttribute txAttr = getTransactionAttributeSource()
        .getTransactionAttribute(method, targetClass);

    PlatformTransactionManager tm = determineTransactionManager(txAttr);

    // Ключовий момент: отримання або створення транзакції
    TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

    Object retVal = null;
    try {
        retVal = invocation.proceedWithInvocation();  // виклик цільового методу
    } catch (Throwable ex) {
        completeTransactionAfterThrowing(txInfo, ex);  // rollback або rollback-to-savepoint
        throw ex;
    } finally {
        cleanupTransactionInfo(txInfo);
    }

    commitTransactionAfterReturning(txInfo);  // commit
    return retVal;
}

AbstractPlatformTransactionManager: handleExistingTransaction

// Ключовий метод для propagation logic
// org.springframework.transaction.support.AbstractPlatformTransactionManager

private TransactionStatus handleExistingTransaction(
        TransactionDefinition definition, Object transaction, boolean debugEnabled) {

    switch (definition.getPropagationBehavior()) {

        case PROPAGATION_NEVER:
            throw new IllegalTransactionStateException(
                "Existing transaction found but propagation is NEVER");

        case PROPAGATION_NOT_SUPPORTED:
            Object suspendedResources = suspend(transaction);
            boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
            return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, suspendedResources);

        case PROPAGATION_REQUIRES_NEW:
            SuspendedResourcesHolder suspendedResources = suspend(transaction);
            // ... створює нову фізичну транзакцію
            return newTransactionStatus(definition, newTransaction, false, ...);

        case PROPAGATION_NESTED:
            if (useSavepointForNestedTransaction()) {
                // Створює JDBC Savepoint
                DefaultTransactionStatus status =
                    prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
                status.createAndHoldSavepoint();
                return status;
            } else {
                // Fallback: поводиться як REQUIRES_NEW (для JTA)
                return handleExistingTransaction(... REQUIRES_NEW logic ...);
            }

        case PROPAGATION_SUPPORTS:
        case PROPAGATION_REQUIRED:
        default:
            // Join існуючої транзакції
            return prepareTransactionStatus(definition, transaction, false, ...);
    }
}

Savepoint Implementation (JDBC)

// org.springframework.jdbc.datasource.JdbcTransactionObjectSupport

public void createSavepoint(String name) {
    try {
        // JDBC 3.0+ Savepoint
        this.savepoint = getConnectionHolder().getConnection().setSavepoint(name);
    } catch (SQLException ex) {
        throw new CannotCreateSavepointException("Could not create JDBC savepoint", ex);
    }
}

public void rollbackToSavepoint() {
    try {
        getConnectionHolder().getConnection().rollback(this.savepoint);
    } catch (SQLException ex) {
        throw new TransactionSystemException("Could not roll back to JDBC savepoint", ex);
    }
}

// Release savepoint (не commit!)
public void releaseSavepoint() {
    try {
        getConnectionHolder().getConnection().releaseSavepoint(this.savepoint);
    } catch (SQLException ex) {
        // Non-fatal — some DBs auto-release on commit
    }
}

Suspend/Resume Mechanics (REQUIRES_NEW)

// Suspend поточної транзакції:
protected final SuspendedResourcesHolder suspend(TransactionDefinition definition) {
    // 1. Unbind connection from ThreadLocal
    ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.unbindResource(obtainDataSource());

    // 2. Suspend transaction synchronizations (Hibernate session, etc.)
    List<TransactionSynchronization> suspendedSynchronizations =
        doSuspendSynchronization();

    // 3. Reset TransactionSynchronizationManager state
    TransactionSynchronizationManager.setActualTransactionActive(false);
    TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
        definition.getIsolationLevel());

    return new SuspendedResourcesHolder(conHolder, suspendedSynchronizations);
}

// Resume після завершення внутрішньої транзакції:
protected final void resume(Object transaction, SuspendedResourcesHolder resourcesHolder) {
    // 1. Re-bind connection to ThreadLocal
    TransactionSynchronizationManager.bindResource(obtainDataSource(), resourcesHolder.getConnectionHolder());

    // 2. Resume synchronizations
    doResumeSynchronization(resourcesHolder.getSuspendedSynchronizations());

    // 3. Restore state
    TransactionSynchronizationManager.setActualTransactionActive(true);
}

Архітектурні Trade-offs

Підхід A: REQUIRED (default)

  • ✅ Плюси: Єдність (atomic commit/rollback), простота, мінімальний overhead, повна сумісність з ORM
  • ❌ Мінуси: Внутрішній виняток → rollback всього ланцюжка, не можна “врятувати” частину роботи
  • Підходить для: 90% бізнес-логіки, де всі операції атомарні

Підхід B: REQUIRES_NEW

  • ✅ Плюси: Ізоляція (внутрішній commit незалежний), audit trail зберігається, можна обробити частковий failure
  • ❌ Мінуси: Дві фізичні транзакції (overhead на suspend/resume), ризик неузгодженості, connection pool usage x2
  • Підходить для: аудиту, логування, notification dispatch, independent side-effects

Підхід C: NESTED (savepoints)

  • ✅ Плюси: Частковий відкат без другої транзакції, єдиний commit, lower overhead ніж REQUIRES_NEW
  • ❌ Мінуси: Не працює з JTA, обмежена підтримка в Hibernate (L1 cache inconsistency), тільки JDBC 3.0+
  • Підходить для: batch processing, bulk operations з partial failure tolerance

Edge Cases та Corner Cases

1. Self-invocation (проксі bypass):

@Service
public class OrderService {

    @Transactional
    public void createOrder(Order order) {
        // BAD: this.logOrder() bypass-ить проксі
        this.logOrder(order);  // REQUIRES_NEW ігнорується!
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logOrder(Order order) {
        auditRepo.save(new AuditEntry(order));
    }
}

Рішення:

// Рішення A: Інжект самого себе
@Service
public class OrderService {
    @Autowired private OrderService self;  // проксі

    @Transactional
    public void createOrder(Order order) {
        self.logOrder(order);  // через проксі — REQUIRES_NEW працює
    }
}

// Рішення Б: AopContext
@EnableAspectJAutoProxy(exposeProxy = true)
// ...
((OrderService) AopContext.currentProxy()).logOrder(order);

// Рішення В: Окремий бін (найкраща практика)
@Service
public class OrderAuditService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logOrder(Order order) { ... }
}

2. Rollback-only mark поширення:

@Transactional
public void outerMethod() {
    try {
        innerService.innerMethod();  // REQUIRED, кидає RuntimeException
    } catch (Exception e) {
        // Зловили виняток, АЛЕ транзакція вже помічена rollback-only!
    }
    // При commit: UnexpectedRollingBackException — транзакція вже marked for rollback
}

Внутрішній механізм:

// AbstractPlatformTransactionManager
public final void rollback(TransactionStatus status) {
    if (status.isRollbackOnly()) {
        // Навіть якщо outer method не кидав виняток,
        // inner method (REQUIRED) вже викликав setRollbackOnly()
        doRollback(status);
        throw new UnexpectedRollingBackException(
            "Transaction rolled back because it has been marked as rollback-only");
    }
}

Коли innerMethod кидає RuntimeException, TransactionInterceptor бачить
виняток ДО того, як outerMethod його catch-ить (бо проксі огортає
виклик). Interceptor викликає setRollbackOnly() на TransactionStatus, і до
моменту, коли outerMethod перехоплює виняток  транзакція вже помічена на відкат.

3. NESTED + Hibernate L1 Cache inconsistency:

@Transactional
public void batchUpdate(List<Entity> entities) {
    for (Entity e : entities) {
        try {
            nestedService.saveNested(e);  // NESTED
        } catch (Exception ex) {
            // Savepoint rollback стався, АЛЕ:
            // Hibernate L1 cache (PersistenceContext) НЕ відкочується!
            // Entity все ще в session.entities, може викликати dirty checking issues
        }
    }
    // При commit: Hibernate flush — може спробувати flush rolled-back entities
}

Workaround:

@PersistenceContext
private EntityManager em;

@Transactional(propagation = Propagation.NESTED)
public void saveNested(Entity e) {
    em.persist(e);
    em.flush();  // Force flush before savepoint
    em.clear();  // Clear L1 cache to avoid stale state
}

4. Propagation та Transaction Synchronization:

// Spring реєструє synchronization callbacks:
TransactionSynchronizationManager.registerSynchronization(
    new TransactionSynchronization() {
        @Override
        public void afterCommit() {
            // Викликається тільки після фінального commit
            // Для REQUIRES_NEW: викликається після commit внутрішньої tx
            // Для NESTED: викликається тільки після commit зовнішньої tx
        }

        @Override
        public void afterCompletion(int status) {
            // Викликається завжди (STATUS_COMMITTED, STATUS_ROLLED_BACK, STATUS_UNKNOWN)
        }
    }
);

5. REQUIRES_NEW та connection pool exhaustion:

Thread-1: outerMethod() — бере connection-1 (suspend)
  Thread-1: innerMethod() — бере connection-2 (REQUIRES_NEW)

  При високому навантаженні:
  - Кожна REQUIRES_NEW = +1 connection
  - 100 concurrent requests × 2 connections = 200 connections needed
  - Pool size = 50 → pool exhaustion → wait timeout

6. Propagation та Reactive (R2DBC):

// Spring Framework 6.x: Reactive transactions працюють інакше
// Немає ThreadLocal, suspend/resume через Project Reactor context

@Transactional
public Mono<Void> processOrder(Order order) {
    return orderRepo.save(order)
        .then(auditService.logOrder(order))  // REQUIRES_NEW через Reactor context switch
        .then();
}

// Propagation для reactive: працює через Context Propagation (Reactors)
// Не всі propagation types fully supported в reactive

Performance Implications

Propagation Overhead Connection Usage Throughput Impact
REQUIRED Мінімальний (~1ms) 1 connection Baseline
REQUIRES_NEW Середній (+5-15ms на suspend/resume) 2 connections (nested) -10-20% при високому навантаженні
NESTED Низький (+1-3ms на savepoint) 1 connection -2-5%
MANDATORY Нульовий 0 (використовує існуючу) 0%
NOT_SUPPORTED Середній (suspend/resume) 0 (під час виконання) +5% (звільняє connection)

Конкретні цифри (Spring Boot 3.x, PostgreSQL, HikariCP pool=50):

  • REQUIRED only: ~25,000 TPS
  • REQUIRED + 1 REQUIRES_NEW: ~20,000 TPS. Overhead: suspend ~2ms, new connection ~0.5ms, Hibernate Session suspend ~2ms, resume ~2ms — разом 5-15ms на виклик.
  • REQUIRED + 1 NESTED: ~24,000 TPS (savepoint overhead мінімальний)
  • 5 nested REQUIRES_NEW: ~10,000 TPS (cascade suspend/resume)

Memory Implications

  • Suspended resources (REQUIRES_NEW): ConnectionHolder + TransactionSynchronizations ~2-5KB per suspend.
  • Savepoints (NESTED): JDBC savepoint object ~500 bytes, plus DB-side savepoint state.
  • ThreadLocal state: TransactionSynchronizationManager зберігає ~1-2KB на thread.
  • Hibernate L1 cache (with NESTED): Може містити stale entities після savepoint rollback — до кількох MB для великих batch операцій.

Concurrency Aspects

REQUIRES_NEW concurrency:

Thread-1: outerMethod(REQUIRED) → suspend → innerMethod(REQUIRES_NEW) → commit → resume
Thread-2: outerMethod(REQUIRED) → suspend → innerMethod(REQUIRES_NEW) → commit → resume

Inner транзакції виконуються паралельно (незалежні connections)
Outer транзакції чекають (suspend), поки inner не завершиться

NESTED concurrency:

Thread-1: outerMethod(REQUIRED) → innerMethod(NESTED) → savepoint → rollback/continue
Thread-2: outerMethod(REQUIRED) → innerMethod(NESTED) → savepoint → rollback/continue

Обидві транзакції використовують свої connections, savepoints незалежні

Real Production Scenario

Ситуація: Фінтех-платформа (2024), Spring Boot 3.2, PostgreSQL, обробка 10,000 платежів/год.

Проблема: При збої платіжного шлюзу (~2% failure rate) аудиторські записи не зберігалися. compliance-відділ не міг відстежити спроби платежів, що порушувало регуляторні вимоги.

Original code:

@Service
public class PaymentService {

    @Autowired private AuditService auditService;  // AuditService.logPayment = REQUIRED

    @Transactional
    public PaymentResult processPayment(Payment payment) {
        paymentRepo.save(payment);

        // Виклик payment gateway — може впасти
        PaymentGatewayResult gatewayResult = gateway.charge(payment);

        // Аудит викликається ПІСЛЯ gateway — якщо gateway впав, аудит не викликається
        auditService.logPayment(payment);  // REQUIRED

        return new PaymentResult(gatewayResult);
    }
}

Root cause analysis:

  1. auditService.logPayment() використовував REQUIRED — був частиною загальної транзакції
  2. При gateway.charge() exception → вся транзакція rollback, включаючи аудит
  3. Compliance вимагав аудитувати ВСІ спроби, включаючи failed

Рішення:

@Service
public class PaymentService {

    @Autowired private AuditService auditService;

    @Transactional
    public PaymentResult processPayment(Payment payment) {
        // Аудит ДО платежу — REQUIRES_NEW, збережеться незалежно
        auditService.logPaymentAttempt(payment);

        paymentRepo.save(payment);

        try {
            PaymentGatewayResult gatewayResult = gateway.charge(payment);
            auditService.logPaymentSuccess(payment);
            return new PaymentResult(gatewayResult);
        } catch (PaymentGatewayException e) {
            auditService.logPaymentFailure(payment, e);
            throw e;  // Payment rollback, але attempt audit вже закомічений
        }
    }
}

@Service
public class AuditService {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logPaymentAttempt(Payment payment) {
        auditRepo.save(new AuditEntry(payment, "ATTEMPT"));
        // Commit — незалежно від outer транзакції
    }
}

Результат:

  • Audit coverage: з 0% (при failure) до 100%
  • Compliance: passed audit
  • Throughput: -5% (REQUIRES_NEW overhead) — прийнятно
  • Connection pool: increased from 50 to 75

Monitoring та Діагностика

Spring Boot Actuator — Transaction metrics:

@Configuration
public class TransactionMonitoringConfig {

    @Bean
    public TransactionSynchronizationEventListener transactionListener(MeterRegistry registry) {
        return new TransactionSynchronizationEventListener() {
            @Override
            public void afterCommit(TransactionSynchronization synchronization) {
                Counter.builder("spring.transaction.commit")
                    .tag("propagation", synchronization.getPropagation())
                    .increment();
            }

            @Override
            public void afterCompletion(TransactionSynchronization synchronization, int status) {
                if (status == STATUS_ROLLED_BACK) {
                    Counter.builder("spring.transaction.rollback")
                        .tag("propagation", synchronization.getPropagation())
                        .increment();
                }
            }
        };
    }
}

Debug logging:

logging:
  level:
    org.springframework.transaction: DEBUG
    org.springframework.orm.jpa: DEBUG

Track suspended transactions:

@Component
public class TransactionMonitoring {

    private final MeterRegistry registry;

    public void trackSuspensions() {
        // Monitor suspended transaction count
        Gauge.builder("spring.transaction.suspended",
            () -> getActiveSuspendedCount())
            .register(registry);
    }
}

Best Practices для Highload

  1. REQUIRED — ваш дефолт для бізнес-логіки — 90% випадків.
  2. REQUIRES_NEW тільки для аудиту/логування — не для пов’язаних даних.
  3. NESTED для batch processing — коли потрібен partial failure tolerance.
  4. Уникайте глибокого nesting — кожен REQUIRES_NEW = +1 connection.
  5. Monitor propagation usage — track через Micrometer.
  6. Self-invocation bypass — завжди викликайте через інший бин.
  7. Tune connection pool — якщо використовуєте REQUIRES_NEW, збільште pool size.
  8. Test rollback propagation — integration tests для кожного сценарію.
  9. Use timeout для REQUIRES_NEW — запобігає зависанню.
  10. Consider programmatic transactions — для складної умовної логіки.

🎯 Шпаргалка для інтерв’ю

Обов’язково знати:

  • Propagation визначає, як методи з @Transactional взаємодіють при вкладених викликах
  • 7 типів: REQUIRED (default), REQUIRES_NEW, NESTED, MANDATORY, SUPPORTS, NOT_SUPPORTED, NEVER
  • REQUIRED = join існуючої або створення нової; REQUIRES_NEW = suspend + нова транзакція
  • NESTED = savepoint всередині існуючої транзакції (тільки JDBC savepoints)
  • Propagation працює тільки через Spring Proxy — self-invocation (this.method()) bypass-ить проксі
  • REQUIRES_NEW overhead: suspend/resume ~5-15ms, +1 connection per call

Часті уточнюючі запитання:

  • Чому inner REQUIRED метод кидає виняток, а outer ловить — все одно rollback? — Inner метод помічає транзакцію rollback-only
  • Чим NESTED відрізняється від REQUIRES_NEW? — NESTED = savepoint (один commit), REQUIRES_NEW = окрема транзакція (окремий commit)
  • Коли використовувати REQUIRES_NEW? — Аудит, логування, notification — дані мають зберегтися незалежно
  • Чи працює propagation з reactive transactions? — Обмежено, через Reactor context propagation

Червоні прапорці (НЕ говорити):

  • “this.method() працює з propagation” — self-invocation bypass-ить проксі
  • “NESTED працює з JTA” — JTA не підтримує savepoints, fallback до REQUIRES_NEW
  • “REQUIRES_NEW не впливає на connection pool” — кожен REQUIRES_NEW = +1 connection

Пов’язані теми:

  • [[14. Що робить Propagation.NESTED]]
  • [[15. В чому різниця між REQUIRED та REQUIRES_NEW]]
  • [[16. Що таке анотація @Transactional]]
  • [[22. Що станеться при виклику @Transactional методу з іншого методу того ж класу]]