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

Что будет, если вызвать Transactional метод из метода без Transactional в Spring

1.0 Junior🔥 41 комментариев
#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

@Transactional из non-Transactional метода: как это работает

Отличный вопрос про Spring AOP и транзакции! Это показывает понимание того как работают прокси в Spring.

Короткий ответ

@Transactional БУДЕТ работать, потому что Spring создаёт прокси-объекты. Но нужно учитывать несколько нюансов.

Как это работает

@Component
public class OrderService {
    
    private final OrderRepository repository;
    
    // БЕЗ @Transactional
    public void createOrder(Order order) {
        System.out.println("Creating order");
        saveOrder(order); // Вызываем другой метод
    }
    
    // С @Transactional
    @Transactional
    public void saveOrder(Order order) {
        repository.save(order); // Это БУДЕТ в транзакции!
    }
}

// Это работает, потому что:
// 1. Spring создаёт прокси-объект OrderService
// 2. Прокси перехватывает вызов saveOrder()
// 3. Прокси открывает транзакцию
// 4. Вызывает оригинальный метод
// 5. Прокси коммитит транзакцию

Внутренняя механика Spring AOP

// Без Spring AOP (обычный класс):
OrderService service = new OrderService(repository);
service.createOrder(order);

// С Spring AOP (если класс в контексте):
OrderService service = (OrderService) Proxy.newProxyInstance(
    OrderService.class.getClassLoader(),
    new Class[]{OrderService.class},
    new TransactionInterceptor() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) {
            if (method.isAnnotatedWith(Transactional.class)) {
                // Открываем транзакцию
                TransactionStatus status = transactionManager.getTransaction(...);
                try {
                    // Вызываем оригинальный метод
                    Object result = method.invoke(target, args);
                    // Коммитим
                    transactionManager.commit(status);
                    return result;
                } catch (Exception e) {
                    // Откатываем
                    transactionManager.rollback(status);
                    throw e;
                }
            } else {
                // Просто вызываем метод
                return method.invoke(target, args);
            }
        }
    }
);

Ключевой момент: через this vs через прокси

✅ РАБОТАЕТ — вызов через Spring контекст:

@Component
public class OrderService {
    
    // Вызов из другого компонента через Spring
    @Autowired
    private OrderService self; // Self-reference
    
    public void createOrder(Order order) {
        // Вызов через self (через прокси) — РАБОТАЕТ!
        self.saveOrder(order);
    }
    
    @Transactional
    public void saveOrder(Order order) {
        repository.save(order); // В транзакции
    }
}

❌ НЕ РАБОТАЕТ — вызов через this:

@Component
public class OrderService {
    
    public void createOrder(Order order) {
        // Вызов через this — БЕЗ прокси! НЕ РАБОТАЕТ!
        this.saveOrder(order);
    }
    
    @Transactional
    public void saveOrder(Order order) {
        repository.save(order); // НЕ в транзакции!
        // Потому что this обходит прокси
    }
}

Практический пример

@Component
public class UserService {
    
    private final UserRepository userRepository;
    private final EmailService emailService;
    
    public UserService(UserRepository userRepository, EmailService emailService) {
        this.userRepository = userRepository;
        this.emailService = emailService;
    }
    
    // Без @Transactional
    public void registerUser(UserRegistrationRequest request) {
        User newUser = new User(
            request.getEmail(),
            request.getName()
        );
        
        // Вызываем @Transactional метод
        User savedUser = saveUserToDatabase(newUser);
        
        // После того как сохраниться в БД
        sendWelcomeEmail(savedUser);
    }
    
    // С @Transactional
    @Transactional
    public User saveUserToDatabase(User user) {
        // Это выполняется в транзакции
        return userRepository.save(user);
    }
    
    // Может быть с @Transactional или без
    public void sendWelcomeEmail(User user) {
        emailService.sendEmail(user.getEmail(), "Welcome!");
    }
}

// Когда мы вызываем:
UserService service = applicationContext.getBean(UserService.class);
service.registerUser(request); // Работает правильно!

Проблема: this reference (self-invocation)

@Component
public class BadExampleService {
    
    private final UserRepository userRepository;
    
    public BadExampleService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    public void register(User user) {
        System.out.println("Starting registration");
        // ❌ ПРОБЛЕМА: вызов через this
        this.saveUser(user); // НЕ работает транзакция!
    }
    
    @Transactional
    public void saveUser(User user) {
        userRepository.save(user);
        
        // Эта ошибка НЕ будет rollback
        if (user.getEmail().contains("bad")) {
            throw new IllegalArgumentException("Bad email");
        }
    }
}

// Почему не работает?
// 1. Spring создаёт прокси BadExampleService
// 2. register() вызывается через прокси (ОК)
// 3. register() вызывает this.saveUser() (БЕЗ прокси!)
// 4. Транзакция НЕ открывается
// 5. При исключении НЕ происходит rollback

Решение 1: Внедрить себя

@Component
public class FixedService {
    
    private final UserRepository userRepository;
    private final FixedService self; // Самовнедрение
    
    public FixedService(UserRepository userRepository, FixedService self) {
        this.userRepository = userRepository;
        this.self = self;
    }
    
    public void register(User user) {
        System.out.println("Starting registration");
        // ✅ ПРАВИЛЬНО: вызов через self (через прокси)
        self.saveUser(user);
    }
    
    @Transactional
    public void saveUser(User user) {
        userRepository.save(user);
    }
}

Решение 2: Использовать ApplicationContext

@Component
public class AnotherFixedService implements ApplicationContextAware {
    
    private final UserRepository userRepository;
    private ApplicationContext context;
    
    @Override
    public void setApplicationContext(ApplicationContext context) {
        this.context = context;
    }
    
    public void register(User user) {
        // Получаем через контекст (получаем прокси)
        AnotherFixedService service = context.getBean(AnotherFixedService.class);
        service.saveUser(user); // Работает!
    }
    
    @Transactional
    public void saveUser(User user) {
        userRepository.save(user);
    }
}

Решение 3: Разделить методы на разные компоненты

// Лучший подход — разделение ответственности

@Component
public class UserRegistrationService {
    
    private final UserPersistenceService persistenceService;
    private final NotificationService notificationService;
    
    public UserRegistrationService(
            UserPersistenceService persistenceService,
            NotificationService notificationService) {
        this.persistenceService = persistenceService;
        this.notificationService = notificationService;
    }
    
    public void register(User user) {
        // Вызываем другой компонент (автоматически через прокси)
        User savedUser = persistenceService.save(user);
        notificationService.sendWelcome(savedUser);
    }
}

@Component
public class UserPersistenceService {
    private final UserRepository userRepository;
    
    public UserPersistenceService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Transactional
    public User save(User user) {
        return userRepository.save(user);
    }
}

@Component
public class NotificationService {
    // ...
}

Режимы проксирования Spring

// PROXY mode (default)
@EnableTransactionManagement(mode = AdviceMode.PROXY)
public class TransactionConfig {
    // Использует Java dynamic proxies
    // Не работает с this reference
}

// ASPECTJ mode
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
public class TransactionConfig {
    // Использует compile-time или load-time weaving
    // РАБОТАЕТ с this reference!
    // Но требует aspectj dependency
}

Best Practices

✅ ПРАВИЛЬНО:

  1. Разделить на разные компоненты (рекомендуется)
  2. Вызывать через Spring контекст
  3. Использовать @Transactional на сервис методах
  4. Тестировать транзакции (не забывайте @Transactional на тестах!)

❌ ИЗБЕГАТЬ:

  1. Вызовов через this из методов со значимой логикой
  2. Цепочек this.method() calls
  3. Наивного предположения что this работает

Практический тест

@SpringBootTest
@Transactional // Откат изменений после теста
public class TransactionTest {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    public void testTransactionalCall() {
        User user = new User("test@example.com", "Test");
        
        // registerUser вызывает saveUserToDatabase(@Transactional)
        userService.registerUser(createRequest(user));
        
        // Проверяем что транзакция сработала
        User saved = userRepository.findByEmail("test@example.com");
        assertNotNull(saved);
    }
}

Итог

  • @Transactional БУДЕТ работать когда метод вызывается через Spring прокси
  • Не работает через this — это основная ловушка
  • Лучшее решение — разделить код на разные компоненты
  • Всегда тестируйте транзакции чтобы убедиться что они работают
  • Помните про AspectJ mode если нужна особая поддержка this reference
Что будет, если вызвать Transactional метод из метода без Transactional в Spring | PrepBro