← Назад к вопросам
В чем особенность вызова метода из метода, когда оба метода @Transactional
3.0 Senior🔥 171 комментариев
#Spring Framework
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Особенность вызова @Transactional методов
Это одна из самых частых проблем в Spring приложениях, которая приводит к трудноловимым ошибкам. Когда один @Transactional метод вызывает другой @Transactional метод в том же классе, второй метод не будет обёрнут в транзакцию.
Почему это происходит?
Spring использует динамические прокси для реализации @Transactional. Когда вы вызываете @Transactional метод, прокси перехватывает вызов и оборачивает его в транзакцию. Однако, когда один метод вызывает другой метод в том же объекте (через this.method()), вызов идёт напрямую к оригинальному объекту, минуя прокси.
Пример проблемы
@Service
public class UserService {
@Transactional
public void createUser(User user) {
// Сохраняем основные данные
userRepository.save(user);
// Этот вызов НЕ будет в отдельной транзакции!
this.sendNotification(user);
}
@Transactional
public void sendNotification(User user) {
// Если здесь произойдёт ошибка,
// она НЕ будет откачена отдельно
notificationService.send(user.getEmail());
}
}
Решение 1: Вынести в другой сервис
@Service
public class UserService {
@Autowired
private NotificationService notificationService;
@Transactional
public void createUser(User user) {
userRepository.save(user);
// Теперь вызов идёт через прокси
notificationService.sendNotification(user);
}
}
@Service
public class NotificationService {
@Transactional
public void sendNotification(User user) {
notificationRepository.save(new Notification(user));
}
}
Решение 2: Использовать ApplicationContext
@Service
public class UserService {
@Autowired
private ApplicationContext context;
@Transactional
public void createUser(User user) {
userRepository.save(user);
// Получаем прокси через контекст
UserService proxy = context.getBean(UserService.class);
proxy.sendNotification(user);
}
@Transactional
public void sendNotification(User user) {
notificationRepository.save(new Notification(user));
}
}
Решение 3: Использовать самоинъекцию (Self-injection)
@Service
public class UserService {
@Autowired
private UserService self;
@Transactional
public void createUser(User user) {
userRepository.save(user);
self.sendNotification(user);
}
@Transactional
public void sendNotification(User user) {
notificationRepository.save(new Notification(user));
}
}
Важные моменты
- Пропагация транзакций: параметр
propagationопределяет, как себя вести при вложенных транзакциях (REQUIRED, REQUIRES_NEW, NESTED и т.д.) - REQUIRES_NEW: создаст новую транзакцию, независимую от родительской. Внешняя откат не затронет внутреннюю
- REQUIRED (по умолчанию): если уже есть транзакция, использует её; если нет, создаёт новую
Пример с REQUIRES_NEW
@Transactional
public void createUser(User user) {
userRepository.save(user);
try {
sendNotification(user);
} catch (Exception e) {
// Ошибка в отправке не откатит сохранение пользователя
log.error("Failed to send notification", e);
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendNotification(User user) {
notificationRepository.save(new Notification(user));
}
Лучший подход — выносить бизнес-логику с разными требованиями к транзакциям в разные сервисы. Это делает код яснее и избегает неожиданных проблем.