← Назад к вопросам
Что произойдет при вызове публичного метода с @Transactional, который вызывает приватный метод с @Transactional REQUIRES_NEW?
2.0 Middle🔥 121 комментариев
#Spring Boot и Spring Data#Spring Framework
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Spring @Transactional и приватные методы
Это коварный вопрос о том, как Spring AOP работает с приватными методами. Ответ вас удивит!
Краткий ответ
Аннотация @Transactional на приватном методе игнорируется, даже если она REQUIRES_NEW.
Почему? Spring использует динамические прокси, а приватные методы нельзя вызвать через прокси.
Как работают Spring прокси
Когда вы аннотируете класс с @Transactional, Spring создаёт прокси-обёртку:
@Service
public class UserService {
@Transactional
public void createUser(String name) { // Публичный - работает!
// Весь код внутри транзакции
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
private void sendNotification() { // Приватный - игнорируется!
// Это НЕ будет новой транзакцией
}
}
// Spring создаёт прокси:
class UserServiceProxy extends UserService {
@Override
public void createUser(String name) {
TransactionManager.begin();
try {
super.createUser(name);
TransactionManager.commit();
} catch (Exception e) {
TransactionManager.rollback();
}
}
// private методы вообще не оборачиваются!
}
Практический пример
@Service
public class UserService {
@Transactional
public void registerUser(String email, String password) {
// Находимся в транзакции 1
User user = new User(email, password);
userRepository.save(user); // Сохранено в БД
// Вызов приватного метода - это обычный вызов Java!
sendWelcomeEmail(user); // НЕ создаёт новую транзакцию!
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
private void sendWelcomeEmail(User user) {
// Это выполнится в ТОЙ ЖЕ транзакции, что и registerUser()
// Аннотация @Transactional игнорируется!
emailService.send(user.getEmail(), "Welcome!");
}
}
// При вызове:
UserService service = applicationContext.getBean(UserService.class);
service.registerUser("user@example.com", "password");
// Оба метода выполняются в ОДНОЙ транзакции
Почему так происходит?
Spring прокси работает через интерфейсы (или CGLIB):
// Прокси могут перехватывать только публичные методы
UserServiceProxy implements UserService {
@Override
public void registerUser(String email, String password) {
// Перехвачено - началась транзакция 1
target.registerUser(email, password);
}
}
// Внутри registerUser это уже обычный код Java
public void registerUser(String email, String password) {
user = new User(email, password);
userRepository.save(user);
this.sendWelcomeEmail(user); // this.sendWelcomeEmail = обычный вызов!
// Даже если sendWelcomeEmail публичный!
}
Жизненный цикл:
- Вызывается
proxy.registerUser()- перехвачено, начинается транзакция 1 - Внутри вызывается
this.sendWelcomeEmail()- это call на реальный объект, не прокси! - Прокси не знает об этом вызове
@Transactional(REQUIRES_NEW)игнорируется
Решение 1: Сделать метод публичным
@Service
public class UserService {
@Transactional
public void registerUser(String email, String password) {
User user = new User(email, password);
userRepository.save(user);
sendWelcomeEmail(user); // Вызов публичного метода
}
@Transactional(propagation = Propagation.REQUIRES_NEW) // ✅ Теперь работает!
public void sendWelcomeEmail(User user) {
emailService.send(user.getEmail(), "Welcome!");
}
}
Решение 2: Самовызов через ApplicationContext
@Service
public class UserService {
@Autowired
private ApplicationContext context;
@Transactional
public void registerUser(String email, String password) {
User user = new User(email, password);
userRepository.save(user);
// Вызываем через прокси!
UserService proxy = context.getBean(UserService.class);
proxy.sendWelcomeEmail(user); // ✅ REQUIRES_NEW работает!
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendWelcomeEmail(User user) {
emailService.send(user.getEmail(), "Welcome!");
}
}
НО это плохая практика - усложняет тестирование!
Решение 3: Выделить в отдельный сервис
// Новый сервис для уведомлений
@Service
public class NotificationService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendWelcomeEmail(User user) {
emailService.send(user.getEmail(), "Welcome!");
}
}
// Основной сервис
@Service
public class UserService {
@Autowired
private NotificationService notificationService;
@Transactional
public void registerUser(String email, String password) {
User user = new User(email, password);
userRepository.save(user);
// Вызов через впрыснутый сервис
notificationService.sendWelcomeEmail(user); // ✅ Новая транзакция!
}
}
Это правильное решение - разделение ответственности!
Реальный пример: где это критично
@Service
public class OrderService {
@Transactional
public void processOrder(Order order) {
// Транзакция 1
order.setStatus(OrderStatus.PROCESSING);
orderRepository.save(order);
// Важно: если sendNotification() откатится,
// заказ всё равно должен сохраниться!
try {
sendOrderConfirmation(order); // Должна быть отдельной транзакцией
} catch (Exception e) {
// Логируем, но не откатываем заказ
logger.error("Failed to send confirmation", e);
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
private void sendOrderConfirmation(Order order) {
// ❌ ЭТА АННОТАЦИЯ ИГНОРИРУЕТСЯ!
emailService.send(order.getCustomer().getEmail(), "Order confirmed");
}
}
// Результат: если emailService выбросит исключение,
// вся транзакция (включая сохранение заказа) откатится!
// Это ОШИБКА в дизайне!
Исправленная версия
@Service
public class NotificationService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendOrderConfirmation(Order order) {
emailService.send(order.getCustomer().getEmail(), "Order confirmed");
}
}
@Service
public class OrderService {
@Autowired
private NotificationService notificationService;
@Transactional
public void processOrder(Order order) {
// Транзакция 1
order.setStatus(OrderStatus.PROCESSING);
orderRepository.save(order); // Сохранится в любом случае
// Отдельная транзакция
try {
notificationService.sendOrderConfirmation(order);
} catch (Exception e) {
logger.error("Failed to send confirmation", e);
// Заказ уже сохранён, поэтому всё ОК
}
}
}
Проверка: всегда вводит в заблуждение
@Service
public class TestService {
@Transactional
public void publicMethod() {
System.out.println("Transaction: " + TransactionSynchronizationManager.isActualTransactionActive());
// true - мы в транзакции
privateMethod();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
private void privateMethod() {
System.out.println("Transaction: " + TransactionSynchronizationManager.isActualTransactionActive());
// true - но это ОДНА И ТА ЖЕ транзакция, не новая!
}
}
Заключение
Ответ на вопрос:
- Приватный метод с @Transactional(REQUIRES_NEW) игнорируется
- Код выполняется в той же транзакции, что и вызывающий метод
- Причина: Spring прокси не может перехватить приватные методы
Best Practice:
- Не используй @Transactional на приватных методах
- Если нужна отдельная транзакция - выдели в отдельный Service
- Впрысни этот сервис через @Autowired
Запомни: self-invocation (когда класс вызывает свой метод) всегда происходит вне прокси!