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

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

1.7 Middle🔥 111 комментариев
#Базы данных и SQL

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

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

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

Открытие новой транзакции при вызове между сервисами

Этот вопрос касается поведения транзакций в Spring (или других фреймворках) при использовании @Transactional. Ответ зависит от propagation level (уровня распространения транзакции) и того, находятся ли сервисы в одном контексте приложения.

Основная концепция: Propagation Behavior

В Spring есть несколько режимов распространения транзакций:

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

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

Если оба сервиса находятся в одном контексте Spring и используют REQUIRED:

@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService;
    
    @Transactional
    public void processOrder(Order order) {
        // Транзакция 1: открыта
        saveOrder(order);
        
        // Вызов другого сервиса БЕЗ создания новой транзакции
        // Используется СУЩЕСТВУЮЩАЯ транзакция
        paymentService.processPayment(order);
        
        // Одна транзакция для обоих операций
    }
    
    @Transactional
    private void saveOrder(Order order) {
        // Участвует в той же транзакции
        repository.save(order);
    }
}

@Service
public class PaymentService {
    @Transactional  // REQUIRED - присоединится к существующей
    public void processPayment(Order order) {
        // Выполняется в ТОЙ ЖЕ транзакции, открытой OrderService
        paymentRepository.save(new Payment(order));
    }
}

Результат: Одна транзакция. Если случится ошибка в PaymentService, откатится ВСЁ (и Order, и Payment).

ВАЖНО: Это работает только если ты вызываешь через @Autowired инстанс сервиса. Если вызовешь методом напрямую внутри класса (this.method()), прокси не сработает и @Transactional не применится!

// ДО: Неправильно - транзакция не применится
@Service
public class OrderService {
    @Transactional
    public void processOrder(Order order) {
        this.saveOrder(order);  // this.method() - прокси не работает!
    }
    
    @Transactional
    private void saveOrder(Order order) {
        // @Transactional НЕ применится!
    }
}

Сценарий 2: REQUIRES_NEW - создание новой транзакции

Если использовать REQUIRES_NEW, будет создана отдельная транзакция:

@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService;
    
    @Transactional
    public void processOrder(Order order) {
        // Транзакция 1: открыта
        saveOrder(order);
        
        // Вызов PaymentService
        paymentService.processPayment(order);
        // Транзакция 1: всё ещё открыта
        
        if (order.getPrice() > 1000) {
            // Ошибка здесь
            throw new IllegalArgumentException("Сумма слишком большая");
        }
        
        // Откроется откат транзакции 1
    }
}

@Service
public class PaymentService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void processPayment(Order order) {
        // Транзакция 2: НОВАЯ ОТДЕЛЬНАЯ транзакция
        paymentRepository.save(new Payment(order));
        // Транзакция 2: коммитится независимо от основной
    }
}

Результат: Две отдельные транзакции:

  • Если ошибка в processOrder после вызова processPayment → платёж останется (он уже коммитился)
  • Если ошибка в processPayment → откатится только платёж, Order остаётся

Сценарий 3: Разные приложения/БД

Если сервисы находятся в разных приложениях (например, вызов через REST API):

// Приложение А
@Service
public class OrderServiceA {
    @Transactional
    public void processOrder(Order order) {
        repository.save(order);
        
        // REST вызов к приложению Б
        restTemplate.postForObject(
            "http://payment-service/api/payments",
            new PaymentRequest(order),
            Payment.class
        );
        
        // Две РАЗНЫЕ транзакции в разных БД!
    }
}

// Приложение Б
@Service
public class PaymentServiceB {
    @Transactional
    public void processPayment(Order order) {
        paymentRepository.save(new Payment(order));
        // Своя собственная транзакция
    }
}

Результат: Две полностью независимые транзакции в разных БД. Это создаёт проблему:

  • Если приложение А закоммитит Order, а потом B упадёт — данные несогласованны!
  • Требуется использовать распределённые транзакции (2PC) или Saga pattern

Сценарий 4: MANDATORY - обязательная транзакция

@Service
public class PaymentService {
    @Transactional(propagation = Propagation.MANDATORY)
    public void processPayment(Order order) {
        // Ошибка: TransactionRequiredException если нет активной транзакции!
        paymentRepository.save(new Payment(order));
    }
}

// Вызов БЕЗ транзакции
public void somethingElse() {
    paymentService.processPayment(order);
    // TransactionRequiredException!
}

Практическая таблица

PropagationЕсть транзакцияНет транзакцииИспользование
REQUIREDИспользуй существующуюСоздай новуюПо умолчанию, для большинства операций
REQUIRES_NEWСоздай новуюСоздай новуюДля операций, которые должны быть независимыми
SUPPORTSИспользуй существующуюВыполни без транзакцииДля методов, работающих с/без транзакции
MANDATORYИспользуй существующуюОшибка!Когда транзакция обязательна
NOT_SUPPORTEDПриостанови текущуюВыполни без транзакцииДля операций, несовместимых с транзакцией
NEVERОшибка!Выполни без транзакцииКогда транзакция запрещена

Рекомендации для правильной работы

1. Используй REQUIRED по умолчанию

@Service
public class UserService {
    @Transactional  // REQUIRED - достаточно для большинства
    public void createUser(User user) {
        repository.save(user);
    }
}

2. Используй REQUIRES_NEW для независимых операций

@Service
public class NotificationService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void sendNotification(String message) {
        // Отправь уведомление в отдельной транзакции
        // Даже если основная транзакция откатится
    }
}

3. Для распределённых систем используй Saga pattern

@Service
public class OrderSaga {
    // Вместо распределённых транзакций
    // используй последовательность шагов с компенсацией
    
    public void processOrder(Order order) {
        try {
            orderService.createOrder(order);  // Шаг 1
            paymentService.chargePayment(order);  // Шаг 2
        } catch (Exception e) {
            orderService.cancelOrder(order);  // Компенсация
            throw e;
        }
    }
}

4. Всегда проверяй уровень распространения

@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void updateData() { }

@Transactional(propagation = Propagation.REQUIRES_NEW, timeout = 30)
public void longOperation() { }

Вывод

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

Новая транзакция откроется только если:

  • Явно указано REQUIRES_NEW
  • Сервисы находятся в разных приложениях
  • Нет активной транзакции

Неправильная работа с транзакциями — частая причина data-race условий и несогласованности данных. Тщательно выбирай propagation level для каждого случая.

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