← Назад к вопросам
Как Spring обрабатывает вложенные транзакции внутри одного класса
3.0 Senior🔥 131 комментариев
#Spring Framework
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Spring Транзакции: вложенные вызовы в одном классе
Проблема Self-Invocation
Вот основная проблема, которую нужно понимать для интервью:
@Service
public class OrderService {
@Transactional
public void createOrder() {
// Начало транзакции TX1
saveOrder();
// ...
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveOrder() {
// Проблема! @Transactional НЕ будет работать!
// Потому что это self-invocation (вызов из того же класса)
}
}
Когда вы вызываете saveOrder() из createOrder() в одном и том же классе, Spring AOP proxy не работает. Аннотация @Transactional игнорируется.
Почему это происходит
Spring использует AOP (Aspect-Oriented Programming) для обработки @Transactional. Когда вы инъектируете OrderService в контроллер, вы получаете не сам сервис, а прокси объект:
Коды с Proxy:
Controller → @Autowired OrderService (это прокси!) → реальный OrderService
↓
Spring перехватывает вызовы и управляет транзакциями
Self-invocation без Proxy:
реальный OrderService.createOrder()
↓
this.saveOrder() ← это вызов на реальный объект, не на прокси!
Решение 1: Self-Injection (Рекомендуется)
Инъектируйте сам сервис и используйте для внутренних вызовов:
@Service
public class OrderService {
@Autowired
private OrderService self; // Self-injection
@Transactional
public void createOrder() {
// TX1 начало
self.saveOrder(); // Использует прокси!
// TX1 завершение
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveOrder() {
// TX2 - новая транзакция! (из-за REQUIRES_NEW)
// TX1 будет suspended, TX2 выполнится независимо
}
}
Решение 2: Извлечение в отдельный класс (Лучший подход)
Разделите бизнес-логику на несколько сервисов:
@Service
public class OrderService {
@Autowired
private OrderPersistenceService persistenceService;
@Transactional
public void createOrder(Order order) {
// TX1 начало - главная транзакция
validateOrder(order);
persistenceService.save(order); // TX2 - отдельная транзакция
// TX1 завершение
}
private void validateOrder(Order order) {
// Без @Transactional, валидация не требует транзакции
}
}
@Service
public class OrderPersistenceService {
@Autowired
private OrderRepository orderRepository;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void save(Order order) {
// TX2 - новая независимая транзакция
orderRepository.save(order);
}
}
Этот подход лучше потому что:
- Нет циклических зависимостей
- Код более чистый и тестируемый
- Явное разделение ответственности
Решение 3: ObjectProvider (Альтернатива)
@Service
public class OrderService {
@Autowired
private ObjectProvider<OrderService> serviceProvider;
@Transactional
public void createOrder() {
OrderService proxy = serviceProvider.getIfAvailable();
proxy.saveOrder();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveOrder() {
// Новая транзакция
}
}
Propagation: как Spring управляет вложенными транзакциями
REQUIRED (по умолчанию)
@Transactional(propagation = Propagation.REQUIRED)
public void outer() {
// TX1 начало
inner(); // Присоединяется к TX1
// TX1 завершение
}
@Transactional(propagation = Propagation.REQUIRED)
public void inner() {
// Не создаёт новую транзакцию, использует TX1
}
REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRED)
public void outer() {
// TX1 начало
try {
inner(); // TX1 SUSPENDED, TX2 начало
} catch (Exception e) {
// TX1 всё ещё может быть committed!
}
// TX1 продолжение
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void inner() {
// TX2 - полностью независимая транзакция
// Если TX2 откатится, TX1 не откатится
}
NESTED
@Transactional(propagation = Propagation.REQUIRED)
public void outer() {
// TX начало
try {
inner(); // Savepoint создаётся
} catch (Exception e) {
// Откат только до savepoint, TX продолжает работать
}
// TX завершение
}
@Transactional(propagation = Propagation.NESTED)
public void inner() {
// Работает в savepoint TX, не в новой транзакции
}
Практический пример: правильная обработка вложенных операций
@Service
public class PaymentService {
@Autowired
private PaymentRepository paymentRepository;
@Autowired
private AuditService auditService;
@Transactional
public PaymentResult processPayment(Payment payment) {
// TX1 - основная транзакция
// 1. Сохраняем платёж
Payment saved = paymentRepository.save(payment);
// 2. Логируем в отдельной транзакции
// Если аудит упадёт, платёж сохранится
try {
auditService.logPayment(saved);
} catch (Exception e) {
// Логируем ошибку, но платёж успешен
log.error("Audit failed", e);
}
return new PaymentResult(saved.getId());
}
}
@Service
public class AuditService {
@Autowired
private AuditRepository auditRepository;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logPayment(Payment payment) {
// TX2 - независимая транзакция для логирования
AuditLog log = new AuditLog();
log.setPaymentId(payment.getId());
log.setTimestamp(Instant.now());
auditRepository.save(log);
}
}
Как тестировать
@SpringBootTest
public class TransactionTest {
@Autowired
private OrderService orderService;
@Test
@Transactional
public void testNestedTransaction() {
// Test framework автоматически откатит все изменения
orderService.createOrder();
// Проверяем результаты
// Не используйте @Transactional в тестах, если нужны реальные транзакции
}
}
Ошибки, которые нужно избегать
// ❌ ПЛОХО: Self-invocation не работает
@Service
public class Service1 {
@Transactional
public void method1() {
this.method2(); // @Transactional на method2 не сработает!
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method2() {}
}
// ✅ ХОРОШО: Используйте injection
@Service
public class Service1 {
@Autowired
private Service2 service2; // Отдельный класс!
@Transactional
public void method1() {
service2.method2(); // Сработает правильно
}
}
@Service
public class Service2 {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method2() {}
}
Итого
- Self-invocation проблема: Spring AOP не работает при вызове метода внутри того же класса
- Решения:
- Self-injection (работает, но не идеально)
- Разделение на отдельные сервисы (лучший выбор)
- ObjectProvider (редко используется)
- Propagation типы: REQUIRED, REQUIRES_NEW, NESTED имеют разное поведение
- Best practice: избегайте вложенных @Transactional в одном классе, разделяйте логику