← Назад к вопросам
Будет ли выполнена транзакция, если нетранзакционный метод вызывает транзакционный метод в Spring?
2.0 Middle🔥 201 комментариев
#Spring Framework
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Spring транзакции: правильное использование @Transactional
Это коварный вопрос, потому что ответ не интуитивен. Давайте разберемся в деталях как работает @Transactional в Spring.
Проблема: транзакция не срабатывает
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// Нетранзакционный метод
public void createUserWithFriend(String name, String friendName) {
saveUser(name); // Транзакция?
saveUser(friendName); // Транзакция?
}
// Транзакционный метод
@Transactional
public void saveUser(String name) {
User user = new User(name);
userRepository.save(user);
}
}
// Вызов:
UserService service = new UserService();
service.createUserWithFriend("John", "Jane");
Ответ: НЕТ, транзакция НЕ будет выполнена!
Почему? Как работает Spring AOP
Spring использует AOP (Aspect-Oriented Programming) для реализации @Transactional.
Когда ты вызываешь метод через Spring bean:
1. Spring создает PROXY вокруг объекта
2. Proxy перехватывает вызов
3. Proxy проверяет есть ли @Transactional
4. Если есть: открывает транзакцию
5. Если нет: просто вызывает метод
Spring AOP Proxy:
┌─────────────────────────────────┐
│ userService.saveUser("John") │ Вызов через proxy
└──────────────┬──────────────────┘
│
├─ Проверка: есть ли @Transactional? ДА
├─ Открыть транзакцию
├─ Выполнить saveUser
└─ Закрыть транзакцию
КРИТИЧНО: это работает только для вызовов через Spring bean.
Проблемный сценарий
@Service
public class UserService {
@Transactional
public void saveUser(String name) {
User user = new User(name);
userRepository.save(user);
// Транзакция открыта
}
public void createUserWithFriend(String name, String friendName) {
// Вызов этого метода через proxy
// Но когда этот метод вызывает saveUser...
this.saveUser(name); // ОШИБКА! это.saveUser - прямой вызов
this.saveUser(friendName); // ОШИБКА! это.saveUser - прямой вызов
}
}
// Почему ошибка?
// this.saveUser - это вызов на оригинальный объект, НЕ на proxy!
// Proxy не перехватит вызов
// @Transactional не сработает
Диаграмма что происходит
Клиент вызывает:
userService.createUserWithFriend("John", "Jane")
│
├─ Это Spring bean? ДА
├─ Есть @Transactional? НЕТ на createUserWithFriend
├─ Просто вызывай метод
│
└─ Внутри createUserWithFriend:
this.saveUser("John") // this - оригинальный объект!
│
└─ Это Spring bean? НЕТ (это прямой вызов)
└─ Proxy НЕ перехватит
└─ @Transactional НЕ сработает
Решение 1: Правильный способ (inject самого себя)
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private UserService self; // Inject себя! (это proxy)
public void createUserWithFriend(String name, String friendName) {
// Вызываем через self (который является proxy)
self.saveUser(name); // ✅ Транзакция сработает!
self.saveUser(friendName); // ✅ Транзакция сработает!
}
@Transactional
public void saveUser(String name) {
User user = new User(name);
userRepository.save(user);
}
}
Решение 2: Правильный способ (все в одной транзакции)
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// Вся логика в одной транзакционной функции
@Transactional
public void createUserWithFriend(String name, String friendName) {
User user1 = new User(name);
userRepository.save(user1);
User user2 = new User(friendName);
userRepository.save(user2);
// Одна транзакция для обоих
}
}
Решение 3: Использовать ObjectProvider
@Service
public class UserService {
@Autowired
private ObjectProvider<UserService> userServiceProvider;
public void createUserWithFriend(String name, String friendName) {
// Получить proxy через provider
UserService proxy = userServiceProvider.getIfAvailable();
proxy.saveUser(name); // ✅ Транзакция сработает!
proxy.saveUser(friendName); // ✅ Транзакция сработает!
}
@Transactional
public void saveUser(String name) {
User user = new User(name);
userRepository.save(user);
}
}
Более сложный сценарий: rollback
@Service
public class UserService {
@Transactional
public void saveUser(String name) {
User user = new User(name);
userRepository.save(user);
}
public void createUserWithFriend(String name, String friendName) {
this.saveUser(name); // БЕЗ транзакции (this вызов)
this.saveUser(friendName); // БЕЗ транзакции (this вызов)
// Если здесь выбросим исключение, что произойдет?
throw new RuntimeException("Error!");
}
}
// Результат:
// - Оба user'а созданы в БД (каждый в своей separate transaction)
// - Исключение выброшено
// - Rollback НЕ произойдет (каждый saveUser закомитился сразу)
Правильный способ (с откатом)
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private UserService self; // Proxy
public void createUserWithFriend(String name, String friendName) {
try {
self.createUserWithFriendTransactional(name, friendName);
} catch (RuntimeException e) {
log.error("Failed to create users", e);
throw e;
}
}
@Transactional // Одна транзакция для всего
public void createUserWithFriendTransactional(String name, String friendName) {
User user1 = new User(name);
userRepository.save(user1);
User user2 = new User(friendName);
userRepository.save(user2);
// Если здесь ошибка - ВСЁ откатится
if (shouldFail()) {
throw new RuntimeException("Error!");
}
}
}
// Результат:
// - Если ошибка - оба user'а НЕ будут созданы (полный rollback)
// - Если успех - оба user'а созданы в одной транзакции
Тестирование
@Service
public class ProblematicService {
@Autowired
private UserRepository userRepository;
public void publicMethod() {
this.transactionalMethod(); // ❌ Транзакция НЕ сработает
}
@Transactional
public void transactionalMethod() {
userRepository.save(new User("John"));
}
}
@Test
public void shouldFail_transactionNotWorking() {
// Создаем реальный объект (БЕЗ proxy)
ProblematicService service = new ProblematicService();
service.publicMethod();
// Транзакция НЕ работала, БЕЗ rollback
// User создан в БД (если база поддерживает autocommit)
}
@Test
public void shouldWork_usingBean(@Autowired ProblematicService service) {
service.publicMethod();
// Через Spring bean (@Autowired), это работает
}
Когда еще НЕ работает @Transactional
// 1. Private метод
@Transactional
private void privateMethod() { // ❌ Spring AOP не может proxy private
// Транзакция НЕ сработает
}
// 2. Final метод
@Transactional
public final void finalMethod() { // ❌ AOP не может переопределить final
// Транзакция НЕ сработает
}
// 3. Final класс
@Transactional
public final class FinalService { // ❌ AOP не может создать proxy final класса
public void method() {
// Транзакция НЕ сработает
}
}
// 4. Вызов БЕЗ Spring container
ProblematicService service = new ProblematicService(); // Без @Autowired
service.publicMethod(); // ❌ Нет proxy
Best practices
// ✅ Правильно: все логику в одну транзакцию
@Service
public class GoodService {
@Transactional
public void createUserWithFriend(String name, String friendName) {
// Весь код в одной транзакции
}
}
// ✅ Правильно: разные уровни транзакций (если нужно)
@Service
public class BetterService {
@Autowired
private UserRepository userRepository;
@Autowired
private BetterService self; // Inject proxy
@Transactional(propagation = Propagation.REQUIRED) // По умолчанию
public void method1() {
// Транзакция 1
}
@Transactional(propagation = Propagation.REQUIRES_NEW) // Новая транзакция
public void method2() {
// Транзакция 2 (независима от 1)
}
public void caller() {
self.method1();
self.method2();
}
}
Вывод
Ответ на вопрос: НЕТ, транзакция НЕ будет выполнена, если нетранзакционный метод вызывает транзакционный метод через this.
Почему:
- Spring использует AOP Proxy для @Transactional
- Proxy работает только для вызовов через Spring bean
- Вызов
this.method()— это вызов на оригинальный объект, НЕ на proxy - Proxy НЕ перехватит вызов
- @Transactional НЕ сработает
Как исправить:
- Инъект себя в самого себя
- Вся логика в одном транзакционном методе
- Использовать ObjectProvider
- Или вызывать через Spring bean
Это частая ошибка даже опытных разработчиков!