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

Что произойдет при вызове методом без @Transactional метода с @Transactional?

2.4 Senior🔥 221 комментариев
#Spring Framework#Базы данных и SQL

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

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

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

Ответ: Что произойдет при вызове методом без @Transactional метода с @Transactional

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

Аннотация @Transactional НЕ будет работать. Вместо создания новой транзакции, будет использована существующая транзакция вызывающего метода (если она есть). Это связано с тем, что Spring использует прокси для управления транзакциями, и прокси работает только при вызове через контейнер Spring.

Как работает @Transactional в Spring?

Spring использует AOP (Aspect-Oriented Programming) для управления транзакциями. Вместо реального класса вы работаете с прокси:

@Service
public class UserService {
    
    // Реальный метод
    @Transactional
    public User createUser(String name) {
        // Код
    }
}

// Spring оборачивает в прокси:
public class UserService_Proxy extends UserService {
    
    @Override
    public User createUser(String name) {
        // ✅ Spring открывает транзакцию
        Transaction tx = transactionManager.begin();
        try {
            User result = super.createUser(name);
            // ✅ Spring коммитит
            tx.commit();
            return result;
        } catch (Exception e) {
            // ✅ Spring откатывает
            tx.rollback();
            throw e;
        }
    }
}

Сценарий: Метод без @Transactional вызывает метод с @Transactional

@Service
public class OrderService {
    
    @Autowired
    private UserService userService;  // Инъекция зависимости
    
    // Метод БЕЗ @Transactional
    public void processOrder(Order order) {
        System.out.println("Processing order...");
        
        // Вызываем метод С @Transactional
        User user = userService.createUser("John");
        
        // ...
    }
}

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    // Метод С @Transactional
    @Transactional
    public User createUser(String name) {
        User user = new User();
        user.setName(name);
        return userRepository.save(user);  // Сохраняем в БД
    }
}

Что произойдет?

@Transactional В МЕТОДЕ createUser() РАБОТАЕТ НОРМАЛЬНО:

Вызов через прокси:

orderService.processOrder(order)  (БЕЗ транзакции)
    ↓
proxy_orderService.processOrder(order)
    ↓
Внутри processOrder вызываем userService.createUser()
    ↓
⭐ proxy_userService.createUser()  ← ВЫЗОВ ЧЕРЕЗ ПРОКСИ!
    ↓
✅ Spring открывает НОВУЮ транзакцию
    ↓
Этот метод выполняется в своей транзакции
    ↓
✅ Spring коммитит после выполнения

Результат: @Transactional в createUser() РАБОТАЕТ.

Пример: Это работает

@Service
public class OrderService {
    
    @Autowired
    private UserService userService;
    
    // БЕЗ @Transactional
    public void processOrder(Order order) {
        // Нет открытой транзакции
        System.out.println("No transaction in processOrder");
        
        // Вызываем createUser() с @Transactional
        User user = userService.createUser("John");
        // ✅ createUser() будет выполнен в СВОЕЙ транзакции
        // ✅ После createUser(), транзакция закроется
        
        // Продолжаем без транзакции
        order.setUser(user);
        // ❌ Это может быть проблемой, если нужна транзакция
    }
}

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Transactional
    public User createUser(String name) {
        // ✅ Открывается транзакция
        User user = new User();
        user.setName(name);
        userRepository.save(user);
        // ✅ Транзакция коммитится
        return user;
    }
}

Проблема: Что если хотим одну транзакцию для обоих методов?

// ❌ ПРОБЛЕМА
@Service
public class OrderService {
    
    @Autowired
    private UserService userService;
    
    // БЕЗ @Transactional
    public void processOrder(Order order) {  // Нет транзакции
        User user = userService.createUser("John");  // Новая транзакция
        
        order.setUser(user);
        // ❌ Здесь нет транзакции для сохранения order
        // Если это упадёт, order не будет сохранён
    }
}

// ✅ РЕШЕНИЕ
@Service
public class OrderService {
    
    @Autowired
    private UserService userService;
    
    @Transactional  // ← Добавляем сюда!
    public void processOrder(Order order) {  // Теперь есть транзакция
        User user = userService.createUser("John");
        // ✅ Используется СУЩЕСТВУЮЩАЯ транзакция
        // (а не создаётся новая, благодаря Propagation.REQUIRED)
        
        order.setUser(user);
        // ✅ Всё в одной транзакции
    }
}

Важный момент: Propagation

По умолчанию @Transactional использует Propagation.REQUIRED:

@Transactional(propagation = Propagation.REQUIRED)
public User createUser(String name) {
    // Если транзакция уже открыта — используй её
    // Если нет — создай новую
}
Сценарий 1: processOrder БЕЗ @Transactional
processOrder() → нет транзакции
    ↓
createUser() с @Transactional
    ↓
✅ Нет существующей транзакции → создаёт НОВУЮ

Сценарий 2: processOrder С @Transactional
processOrder() с @Transactional → открывает транзакцию Tx1
    ↓
createUser() с @Transactional(propagation=REQUIRED)
    ↓
✅ Уже есть транзакция Tx1 → ИСПОЛЬЗУЕТ её (не создаёт новую)

Разные Propagation уровни

@Service
public class TransactionService {
    
    // 1. REQUIRED (по умолчанию)
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodRequired() {
        // Использует существующую транзакцию или создаёт новую
    }
    
    // 2. REQUIRES_NEW
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodRequiresNew() {
        // ВСЕГДА создаёт НОВУЮ транзакцию
        // Приостанавливает существующую
    }
    
    // 3. MANDATORY
    @Transactional(propagation = Propagation.MANDATORY)
    public void methodMandatory() {
        // Требует существующей транзакции
        // Выбросит исключение если её нет
    }
    
    // 4. SUPPORTS
    @Transactional(propagation = Propagation.SUPPORTS)
    public void methodSupports() {
        // Если есть транзакция — использует её
        // Если нет — выполняется БЕЗ транзакции
    }
    
    // 5. NOT_SUPPORTED
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void methodNotSupported() {
        // Выполняется БЕЗ транзакции
        // Приостанавливает существующую
    }
    
    // 6. NEVER
    @Transactional(propagation = Propagation.NEVER)
    public void methodNever() {
        // Требует отсутствия транзакции
        // Выбросит исключение если транзакция есть
    }
}

Пример с REQUIRES_NEW

@Service
public class OrderService {
    
    @Autowired
    private UserService userService;
    
    @Transactional
    public void processOrder(Order order) {  // Транзакция 1
        User user = userService.createUser("John");  // Транзакция 2
        order.setUser(user);
    }
}

@Service
public class UserService {
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public User createUser(String name) {
        // ❌ НОВАЯ отдельная транзакция
        // Если упадёт — order.setUser() НЕ откатится
        User user = new User();
        user.setName(name);
        return user;
    }
}

Частая ошибка: Self-invocation (вызов из того же класса)

@Service
public class UserService {
    
    @Transactional
    public void createUserWithLogging(String name) {
        // ✅ Открывается транзакция
        User user = createUser(name);
        logUserCreation(user);
        // ✅ Коммитится
    }
    
    @Transactional(readOnly = true)
    public User createUser(String name) {
        // ❌ ПРОБЛЕМА: вызов из этого же класса!
        // createUser() вызывается как обычный метод,
        // не через прокси
        // @Transactional НЕ применится!
    }
    
    private void logUserCreation(User user) {
        // Логирование
    }
}

Решение: Инъекция через конструктор или использование событий:

// Вариант 1: Разделить на разные сервисы
@Service
class UserCreationService {
    @Autowired
    private UserService userService;
    
    @Transactional
    public void createUserWithLogging(String name) {
        User user = userService.createUser(name);  // Через другой сервис
        logUserCreation(user);
    }
}

// Вариант 2: Spring Events
@Service
class UserService {
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    @Transactional
    public void createUser(String name) {
        User user = new User();
        user.setName(name);
        eventPublisher.publishEvent(new UserCreatedEvent(user));
    }
}

@Component
class UserCreationLogger {
    @EventListener
    public void logUserCreation(UserCreatedEvent event) {
        // Это выполнится после транзакции
    }
}

Пример: Правильно спроектированное приложение

@Service
public class OrderService {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private OrderRepository orderRepository;
    
    // ✅ Одна большая транзакция для всей операции
    @Transactional
    public Order processOrder(OrderRequest request) {
        // 1. Создаём или обновляем пользователя
        User user = userService.createOrUpdateUser(request.getUsername());
        // ✅ Использует СУЩЕСТВУЮЩУЮ транзакцию
        
        // 2. Создаём заказ
        Order order = new Order();
        order.setUser(user);
        order.setItems(request.getItems());
        orderRepository.save(order);
        // ✅ В одной транзакции
        
        // 3. Если что-то упадёт — всё откатится
        return order;
    }
}

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    // По умолчанию Propagation.REQUIRED
    @Transactional
    public User createOrUpdateUser(String username) {
        User user = userRepository.findByUsername(username)
            .orElse(new User(username));
        return userRepository.save(user);
    }
}

Проверка: Работает ли @Transactional?

// Способ 1: Логирование
@Transactional
public void testTransaction() {
    TransactionSynchronizationManager.registerSynchronization(
        new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                System.out.println("✅ Transaction committed");
            }
            
            @Override
            public void afterCompletion(int status) {
                System.out.println("✅ Transaction completed");
            }
        }
    );
}

// Способ 2: Проверка в тесте
@Test
@Transactional
public void testTransactionRollback() {
    userService.createUser("John");
    // После @Test, @Transactional откатит изменения
    
    // Пользователь НЕ будет в БД (откатился)
    assertFalse(userRepository.existsByName("John"));
}

Итог

  1. @Transactional работает через прокси — только при вызове через Spring контейнер
  2. Метод с @Transactional будет работать — даже если вызывающий метод без @Transactional
  3. По умолчанию используется REQUIRED propagation — использует существующую или создаёт новую
  4. Self-invocation — частая ошибка — вызов из того же класса не использует прокси
  5. Правильно спроектируйте транзакции — думайте о границах транзакций на уровне сервиса

Главное правило: Помните, что @Transactional работает через AOP прокси. Вызовите метод через контейнер Spring, и транзакция будет работать, независимо от того, есть ли @Transactional у вызывающего метода.