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

Откроется ли новая транзакция, если в одном сервисе из одного транзакционного метода вызвать другой

2.0 Middle🔥 241 комментариев
#Docker, Kubernetes и DevOps#REST API и микросервисы

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

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

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

Откроется ли новая транзакция в Spring @Transactional методах?

Это важный вопрос о поведении Spring Propagation — одной из самых запутанных частей Spring Framework. Ответ зависит от propagation уровня, и я разберу все сценарии.

Краткий ответ

По умолчанию (REQUIRED): новая транзакция НЕ откроется, будет использована существующая. Но это можно изменить через propagation уровень.

Propagation уровни в Spring

// 1. REQUIRED (по умолчанию) — использует существующую или создаёт новую
@Transactional(propagation = Propagation.REQUIRED)
public void requiredMethod() {
    // Если есть активная транзакция → используем её
    // Если нет → создаём новую
}

// 2. REQUIRES_NEW — всегда создаёт новую транзакцию
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNewMethod() {
    // Приостанавливает текущую транзакцию
    // Создаёт новую
    // После завершения → восстанавливает старую
}

// 3. NESTED — создаёт savepoint внутри существующей
@Transactional(propagation = Propagation.NESTED)
public void nestedMethod() {
    // Только если есть активная транзакция
    // Создаёт точку сохранения (savepoint)
    // Rollback вернёт только до savepoint
}

// 4. MANDATORY — требует существующей транзакции
@Transactional(propagation = Propagation.MANDATORY)
public void mandatoryMethod() {
    // Если нет активной транзакции → исключение
}

// 5. NEVER — НЕ должно быть транзакции
@Transactional(propagation = Propagation.NEVER)
public void neverMethod() {
    // Если есть активная транзакция → исключение
}

// 6. SUPPORTS — использует если есть, но не создаёт
@Transactional(propagation = Propagation.SUPPORTS)
public void supportsMethod() {
    // С транзакцией → работает в ней
    // Без транзакции → работает без неё
}

// 7. NOT_SUPPORTED — игнорирует существующую
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void notSupportedMethod() {
    // Приостанавливает текущую транзакцию
    // Выполняет метод без неё
    // Восстанавливает транзакцию после
}

Практические примеры

Сценарий 1: REQUIRED (по умолчанию)

@Service
public class OrderService {
    
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private PaymentService paymentService;
    
    @Transactional  // REQUIRED по умолчанию
    public void createOrder(OrderDTO dto) {
        // Транзакция 1 открывается
        Order order = new Order(dto);
        orderRepository.save(order);
        
        // Вызываем другой @Transactional метод
        paymentService.processPayment(order.getId());  // Используется ТА ЖЕ транзакция!
        
        // Если processPayment() выбросит исключение → откатится ВСЁ
    }
}

@Service
public class PaymentService {
    
    @Transactional  // REQUIRED по умолчанию
    public void processPayment(Long orderId) {
        // НЕ открывает новую транзакцию
        // Использует транзакцию из createOrder()
        Payment payment = new Payment(orderId);
        paymentRepository.save(payment);
    }
}

// Результат: обе операции в ОДНОЙ транзакции
// Если processPayment() вызовет исключение → оба save() откатятся

Сценарий 2: REQUIRES_NEW (создаёт новую)

@Service
public class OrderService {
    
    @Transactional
    public void createOrder(OrderDTO dto) {
        Order order = new Order(dto);
        orderRepository.save(order);  // В Транзакции 1
        
        try {
            paymentService.processPaymentSafely(order.getId());
            // В Транзакции 2 (REQUIRES_NEW)
        } catch (PaymentException e) {
            // Платёж не прошёл, но заказ уже сохранён в БД!
            log.error("Payment failed but order created");
        }
    }
}

@Service
public class PaymentService {
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void processPaymentSafely(Long orderId) {
        Payment payment = new Payment(orderId);
        paymentRepository.save(payment);  // В отдельной Транзакции 2
        
        if (!isPaymentValid()) {
            throw new PaymentException("Invalid payment");  // Откатит только Транзакцию 2!
        }
    }
}

// Результат: независимые транзакции
// Rollback в processPaymentSafely() НЕ влияет на createOrder()
// Даже если платёж отклонен, заказ остаётся в БД

Сценарий 3: NESTED (savepoint)

@Service
public class OrderService {
    
    @Transactional
    public void createOrder(OrderDTO dto) {
        Order order = new Order(dto);
        orderRepository.save(order);  // Сохраняем
        
        try {
            notificationService.sendEmail(order);
            // При NESTED → создаёт savepoint
        } catch (EmailException e) {
            // Откатит только до savepoint
            // Заказ остаётся сохранённым
            log.warn("Email failed but order created");
        }
    }
}

@Service
public class NotificationService {
    
    @Transactional(propagation = Propagation.NESTED)
    public void sendEmail(Order order) throws EmailException {
        Email email = new Email(order);
        emailRepository.save(email);
        
        if (!emailService.send(email)) {
            throw new EmailException("Failed to send email");
        }
    }
}

// Результат: частичный rollback
// Если email не отправлен → откатит только email.save()
// Order остаётся в ОДНОЙ транзакции

Таблица сравнения

PropagationНовая?Если есть транзакцияЕсли нет транзакцииИспользование
REQUIREDНетИспользуетСоздаётПо умолчанию, безопасно
REQUIRES_NEWДаСоздаёт новуюСоздаётНезависимые операции
NESTEDPartialSavepointОшибкаОткат части операций
MANDATORYНетИспользуетОшибкаТребует контекста
NEVERНетОшибкаOKБез транзакции
SUPPORTSНетИспользуетOKОпциональная транзакция
NOT_SUPPORTEDНетПриостанавливаетOKИгнорирует

Важное замечание: Прокси-объекты

@Service
public class OrderService {
    
    @Transactional
    public void method1() {
        method2();  // ❌ ПРОБЛЕМА!
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void method2() {
        // Новая транзакция НЕ откроется!
        // Потому что method1() вызывает method2() напрямую (this.method2())
        // Минуя Spring прокси
    }
}

// ✅ Решение: инъектировать отдельный бин
@Service
public class OrderService {
    
    @Autowired
    private PaymentService paymentService;  // Отдельный бин с прокси
    
    @Transactional
    public void method1() {
        paymentService.method2();  // ✅ Правильно! Используется прокси
    }
}

Практические рекомендации

  1. Используй REQUIRED (по умолчанию) для основной логики
  2. REQUIRES_NEW для независимых операций (логирование, аудит, платежи)
  3. NESTED редко используется, требует DatabasePlatformSupport
  4. Не вызывай @Transactional методы из this — всегда инъектируй отдельный бин
  5. Помни про readOnly=true для оптимизации read-only операций:
    • @Transactional(readOnly=true) — не будет flush
    • Быстрее на больших объёмах данных

Выводы

Ответ на вопрос: по умолчанию НЕ откроется новая транзакция, будет использована существующая. Это ТРЕБУЕМОЕ поведение для большинства случаев. Если нужна отдельная транзакция — явно указыва propagation = Propagation.REQUIRES_NEW.

Откроется ли новая транзакция, если в одном сервисе из одного транзакционного метода вызвать другой | PrepBro