Откроется ли новая транзакция, если из одного транзакционного метода вызвать другой в разных сервисах
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Открытие новой транзакции при вызове между сервисами
Этот вопрос касается поведения транзакций в 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 для каждого случая.