← Назад к вопросам

В чем особенность вызова метода из метода, когда оба метода @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));
}

Лучший подход — выносить бизнес-логику с разными требованиями к транзакциям в разные сервисы. Это делает код яснее и избегает неожиданных проблем.