← Назад к вопросам

Что такое Propagation.NESTED?

2.3 Middle🔥 181 комментариев
#Stream API и функциональное программирование#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое Propagation.NESTED

Propagation.NESTED — это режим распространения транзакций в Spring, который создаёт вложенную (nested) транзакцию внутри существующей транзакции. Вложенная транзакция может быть откачена независимо, не влияя на родительскую транзакцию, но успех вложенной зависит от успеха родительской.

Контекст: Режимы распространения транзакций

Spring поддерживает 7 режимов распространения (propagation modes):

@Transactional(propagation = Propagation.REQUIRED)      // По умолчанию
@Transactional(propagation = Propagation.SUPPORTS)      // Если есть, используй
@Transactional(propagation = Propagation.MANDATORY)     // Требует наличие
@Transactional(propagation = Propagation.REQUIRES_NEW)  // Всегда новая
@Transactional(propagation = Propagation.NOT_SUPPORTED) // Без транзакции
@Transactional(propagation = Propagation.NEVER)         // Запрещена
@Transactional(propagation = Propagation.NESTED)        // Вложенная (SavePoint)

Как работает NESTED

@Service
public class OrderService {
    
    @Transactional
    public void createOrder(Order order) {
        // Начинается основная транзакция
        saveOrder(order);
        
        try {
            processPayment(order);
        } catch (Exception e) {
            // Если платёж упал, откатываем только платёж
            // но заказ остаётся в БД
            System.out.println("Платёж не прошёл, заказ всё равно создан");
        }
    }
    
    @Transactional(propagation = Propagation.NESTED)
    public void processPayment(Order order) {
        // Это вложенная транзакция (SavePoint в БД)
        // Если здесь выброситься исключение
        // откатятся только изменения внутри этого метода
        // родительская транзакция продолжит работу
        
        paymentGateway.charge(order.getAmount());
        updatePaymentStatus(order);
    }
}

NESTED vs REQUIRES_NEW

Это самая частая путаница:

// Сценарий: Основная транзакция падает

// REQUIRES_NEW: Новая НЕЗАВИСИМАЯ транзакция
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(String message) {
    // Создаётся совершенно новая транзакция
    // Если родительская упадёт, этот лог всё равно сохранится
    logRepository.save(new Log(message));
    // commit() будет вызван независимо
}

// NESTED: Вложенная транзакция (SavePoint)
@Transactional(propagation = Propagation.NESTED)
public void trySaveLog(String message) {
    // Создаётся SavePoint внутри родительской транзакции
    // Если родительская упадёт, этот лог тоже откатится!
    logRepository.save(new Log(message));
    // Откат произойдёт вместе с родительской
}

Визуально:

REQUIRES_NEW:
┌─────────────────────────────┐
│ Родительская транзакция     │
│ ┌───────────────────────────┤
│ │ ..код..                   │
│ │ Exception! ROLLBACK        │
│ └───────────────────────────┘
│ ❌ Откачена                 │
└─────────────────────────────┘
          ↓ (независимо)
┌─────────────────────────────┐
│ REQUIRES_NEW транзакция     │
│ ┌───────────────────────────┤
│ │ ...код...                 │
│ │ COMMIT (выполнится)        │
│ └───────────────────────────┘
│ ✅ Сохранится в БД         │
└─────────────────────────────┘

NESTED:
┌──────────────────────────────────────────┐
│ Родительская транзакция                  │
│ ┌──────────────────────────────────────┐ │
│ │ ..код..                              │ │
│ │ ┌────────────────────────────────┐   │ │
│ │ │ NESTED (SavePoint)             │   │ │
│ │ │ Exception! ROLLBACK to SavePoint│   │ │ (только вложенная откачена)
│ │ └────────────────────────────────┘   │ │
│ │ ..ещё код..                          │ │
│ │ Exception! ROLLBACK everything        │ │
│ └──────────────────────────────────────┘ │
│ ❌ Всё откачена (включая SavePoint)     │
└──────────────────────────────────────────┘

Реальный пример: Обработка платежа

@Service
public class OrderProcessingService {
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private PaymentService paymentService;
    @Autowired
    private LogService logService;
    @Autowired
    private NotificationService notificationService;
    
    @Transactional
    public OrderResult processOrder(Order order) {
        // 1. Сохраняем заказ
        orderRepository.save(order);
        System.out.println("Заказ сохранён: " + order.getId());
        
        // 2. Пытаемся обработать платёж
        try {
            paymentService.processPayment(order);
            System.out.println("Платёж успешен");
        } catch (PaymentException e) {
            System.out.println("Платёж не прошёл, но заказ остался");
            // Заказ уже в БД, даже если платёж упал
        }
        
        // 3. Логируем результат (может упасть, но не влияет на всё)
        try {
            logService.logOrderCreated(order);
        } catch (Exception e) {
            System.out.println("Лог не сохранился, но заказ в БД");
        }
        
        // 4. Отправляем уведомление
        try {
            notificationService.notifyUser(order);
        } catch (Exception e) {
            System.out.println("Уведомление не отправлено");
        }
        
        return new OrderResult(order.getId(), "created");
    }
}

@Service
public class PaymentService {
    @Autowired
    private PaymentGateway gateway;
    @Autowired
    private PaymentRepository paymentRepository;
    
    // NESTED: если платёж упадёт, откатит только платёж
    // родительская транзакция (заказ) останется
    @Transactional(propagation = Propagation.NESTED)
    public void processPayment(Order order) throws PaymentException {
        Payment payment = new Payment();
        payment.setOrderId(order.getId());
        payment.setAmount(order.getTotal());
        payment.setStatus("PROCESSING");
        paymentRepository.save(payment);
        
        try {
            gateway.charge(order.getTotal()); // Может упасть
        } catch (Exception e) {
            payment.setStatus("FAILED");
            paymentRepository.save(payment);
            throw new PaymentException("Не удалось снять платёж");
        }
        
        payment.setStatus("COMPLETED");
        paymentRepository.save(payment);
    }
}

@Service
public class LogService {
    @Autowired
    private AuditLogRepository logRepository;
    
    // NESTED: если логирование упадёт, откатит только лог
    // заказ и платёж (если прошёл) останутся
    @Transactional(propagation = Propagation.NESTED)
    public void logOrderCreated(Order order) {
        AuditLog log = new AuditLog();
        log.setAction("ORDER_CREATED");
        log.setOrderId(order.getId());
        log.setTimestamp(LocalDateTime.now());
        logRepository.save(log);
    }
}

Когда использовать NESTED

Используйте NESTED когда:

  1. Операция опциональна — если она упадёт, остальное должно работать
  2. Нужна откат к точке — SavePoint в БД позволяет откатить только часть
  3. Обработка ошибок в транзакции — можете поймать исключение и продолжить
  4. Логирование ошибок — логируйте ошибку, но продолжайте работу
@Service
public class UserRegistrationService {
    
    @Transactional
    public User registerUser(UserRegistrationRequest request) {
        // Основная работа
        User user = new User(request.getEmail(), request.getName());
        userRepository.save(user);
        
        // Отправляем приветственное письмо (может упасть)
        try {
            sendWelcomeEmail(user);
        } catch (MailException e) {
            // Письмо не отправилось, но пользователь зарегистрирован
            logger.error("Email не отправлен", e);
        }
        
        // Добавляем в рассылку (может упасть)
        try {
            addToNewsletter(user);
        } catch (NewsletterException e) {
            // Рассылка не добавил, но пользователь создан
            logger.error("Не добавили в рассылку", e);
        }
        
        return user;
    }
    
    @Transactional(propagation = Propagation.NESTED)
    private void sendWelcomeEmail(User user) throws MailException {
        // Если упадёт, откатится только эта операция
        mailService.send(user.getEmail(), "Welcome!");
    }
    
    @Transactional(propagation = Propagation.NESTED)
    private void addToNewsletter(User user) throws NewsletterException {
        // Если упадёт, откатится только эта операция
        newsletterRepository.add(user.getEmail());
    }
}

Ограничения NESTED

// 1. Работает не со всеми БД
// NESTED работает с:
// - PostgreSQL ✓
// - Oracle ✓
// - MySQL (InnoDB) ✓
// - SQL Server ✓
// НЕ работает с:
// - SQLite ❌
// - H2 (зависит от версии) ⚠️

// 2. Требует database-specific кода
// Spring должен знать как работают SavePoint в вашей БД

// 3. Производительность
// SavePoint требуют дополнительных ресурсов
// не злоупотребляйте вложенностью

Сравнение всех режимов

public class TransactionPropagationExample {
    
    @Transactional(propagation = Propagation.REQUIRED)
    // Используй существующую или создай новую
    // Если в методе ошибка — вся транзакция откатывается
    void required() {}
    
    @Transactional(propagation = Propagation.SUPPORTS)
    // Если есть транзакция — используй, если нет — не нужна
    void supports() {}
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    // Создай НОВУЮ независимую транзакцию
    // Используй для операций, которые должны всегда завершиться
    void requiresNew() {}
    
    @Transactional(propagation = Propagation.NESTED)
    // Создай SavePoint внутри существующей
    // Может откатиться, не влияя на родительскую
    void nested() {}
    
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    // Выполни БЕЗ транзакции
    void notSupported() {}
    
    @Transactional(propagation = Propagation.NEVER)
    // Запрещена транзакция, выброси ошибку если была
    void never() {}
    
    @Transactional(propagation = Propagation.MANDATORY)
    // Требует ОБЯЗАТЕЛЬНОЕ наличие транзакции
    void mandatory() {}
}

Заключение

Propagation.NESTED — это режим распространения, который:

  1. Создаёт SavePoint — точку для отката внутри транзакции
  2. Независимый откат — вложенная часть может откатиться отдельно
  3. Зависит от родителя — если родитель упадёт, упадёт и вложенная
  4. Для опциональных операций — логирование, уведомления, рассылки
  5. Требует поддержки БД — не все СУБД поддерживают SavePoint

Используйте NESTED для операций, которые должны быть откачены независимо, если они упадут, но всё равно зависят от успеха родительской транзакции.