← Назад к вопросам
Что такое 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 когда:
- Операция опциональна — если она упадёт, остальное должно работать
- Нужна откат к точке — SavePoint в БД позволяет откатить только часть
- Обработка ошибок в транзакции — можете поймать исключение и продолжить
- Логирование ошибок — логируйте ошибку, но продолжайте работу
@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 — это режим распространения, который:
- Создаёт SavePoint — точку для отката внутри транзакции
- Независимый откат — вложенная часть может откатиться отдельно
- Зависит от родителя — если родитель упадёт, упадёт и вложенная
- Для опциональных операций — логирование, уведомления, рассылки
- Требует поддержки БД — не все СУБД поддерживают SavePoint
Используйте NESTED для операций, которые должны быть откачены независимо, если они упадут, но всё равно зависят от успеха родительской транзакции.