← Назад к вопросам
Откроется ли новая транзакция, если в одном сервисе из одного транзакционного метода вызвать другой
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 | Да | Создаёт новую | Создаёт | Независимые операции |
| NESTED | Partial | Savepoint | Ошибка | Откат части операций |
| 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(); // ✅ Правильно! Используется прокси
}
}
Практические рекомендации
- Используй REQUIRED (по умолчанию) для основной логики
- REQUIRES_NEW для независимых операций (логирование, аудит, платежи)
- NESTED редко используется, требует DatabasePlatformSupport
- Не вызывай @Transactional методы из this — всегда инъектируй отдельный бин
- Помни про readOnly=true для оптимизации read-only операций:
@Transactional(readOnly=true)— не будет flush- Быстрее на больших объёмах данных
Выводы
Ответ на вопрос: по умолчанию НЕ откроется новая транзакция, будет использована существующая. Это ТРЕБУЕМОЕ поведение для большинства случаев. Если нужна отдельная транзакция — явно указыва propagation = Propagation.REQUIRES_NEW.