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

Что произойдет при вызове @Transactional(Propagation.NEVER) метода в Spring?

2.0 Middle🔥 111 комментариев
#Spring Boot и Spring Data#Spring Framework

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

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

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

# @Transactional(Propagation.NEVER) в Spring

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

Propagation.NEVER означает: если метод вызывается БЕЗ активной транзакции — всё работает нормально. Если вызывается С активной транзакцией — выбрасывается исключение IllegalTransactionStateException.

@Transactional(propagation = Propagation.NEVER)
public void doSomething() {
    // Если мы здесь внутри транзакции — ОШИБКА!
    // Если вне транзакции — нормально
}

Поведение Propagation.NEVER

СценарийРезультат
Вызов БЕЗ транзакции✅ Метод выполняется нормально (БЕЗ транзакции)
Вызов ВНУТРИ транзакции❌ Выбрасывается IllegalTransactionStateException

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

Пример 1: Вызов БЕЗ транзакции (успех)

@Service
public class UserService {
    
    @Transactional(propagation = Propagation.NEVER)
    public String readUserName(Long id) {
        // Метод может быть вызван без транзакции
        // Например, для чтения из кэша или статических данных
        return "John";
    }
}

// В контроллере
@RestController
public class UserController {
    @GetMapping("/user/{id}/name")
    public String getName(@PathVariable Long id) {
        // Нет транзакции — успех
        return userService.readUserName(id);  // ✅ OK
    }
}

Пример 2: Вызов ВНУТРИ транзакции (ошибка)

@Service
public class OrderService {
    
    @Transactional  // REQUIRED — транзакция активна
    public void createOrder(Long userId) {
        User user = userService.readUserName(userId);  // ❌ ОШИБКА!
        // IllegalTransactionStateException: 
        // Existing transaction found for transaction marked with propagation 'never'
        
        // Создаём заказ
        Order order = new Order(user);
        orderRepository.save(order);
    }
}

Когда использовать Propagation.NEVER?

1. Операции, которые НЕ должны быть в транзакции

Читение данных из кэша или внешнего API:

@Service
public class CacheService {
    
    @Transactional(propagation = Propagation.NEVER)
    public String getCachedValue(String key) {
        // Чтение из Redis
        // Это быстрая операция, не требует БД-транзакции
        return redisTemplate.opsForValue().get(key);
    }
}

2. Логирование и мониторинг

Операции, которые должны быть независимыми от основной транзакции:

@Service
public class AuditService {
    
    @Transactional(propagation = Propagation.NEVER)
    public void logUserAction(String userId, String action) {
        // Логирование в отдельную таблицу
        // Не должно влиять на основную транзакцию
        AuditLog log = new AuditLog(userId, action);
        auditRepository.save(log);
    }
}

3. Защита от случайного вложения транзакций

Гарантирует, что метод НИКОГДА не будет вызван в контексте транзакции:

@Service
public class UtilityService {
    
    @Transactional(propagation = Propagation.NEVER)
    public int calculateHash(String data) {
        // Утилитарная функция, не работающая с БД
        // Гарантируем, что нет транзакции
        return data.hashCode();
    }
}

Сравнение с другими значениями Propagation

PropagationБез транзакцииВнутри транзакции
REQUIRED (по умолчанию)Создаёт новуюИспользует существующую
REQUIRES_NEWСоздаёт новуюСоздаёт новую (вложенную)
SUPPORTSБЕЗ транзакцииИспользует существующую
NOT_SUPPORTEDБЕЗ транзакцииПриостанавливает текущую
MANDATORYВыбрасывает ошибкуИспользует существующую
NEVERБЕЗ транзакцииВыбрасывает ошибку

Полный пример

@Service
public class PaymentService {
    
    private final BillingService billingService;
    private final AuditService auditService;
    private final PaymentRepository paymentRepository;
    
    @Autowired
    public PaymentService(BillingService billingService,
                         AuditService auditService,
                         PaymentRepository paymentRepository) {
        this.billingService = billingService;
        this.auditService = auditService;
        this.paymentRepository = paymentRepository;
    }
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void processPayment(Long orderId, BigDecimal amount) {
        // Основная логика внутри транзакции
        Payment payment = new Payment(orderId, amount);
        paymentRepository.save(payment);
        
        // Это вызовет ошибку, т.к. мы внутри транзакции!
        auditService.logPayment(orderId, amount);  // ❌ IllegalTransactionStateException
    }
    
    // Правильный подход
    @Transactional(propagation = Propagation.REQUIRED)
    public void processPaymentCorrect(Long orderId, BigDecimal amount) {
        Payment payment = new Payment(orderId, amount);
        paymentRepository.save(payment);
    }
    
    @Transactional(propagation = Propagation.NEVER)
    public void logPayment(Long orderId, BigDecimal amount) {
        // Логирование без транзакции
        AuditLog log = new AuditLog("Payment: " + orderId, amount.toString());
        auditRepository.save(log);
    }
    
    // Вызов из контроллера
    public void completePayment(Long orderId, BigDecimal amount) {
        processPaymentCorrect(orderId, amount);
        logPayment(orderId, amount);  // ✅ Вызывается БЕЗ транзакции
    }
}

Исключение

Когда вы нарушаете правило NEVER:

org.springframework.transaction.IllegalTransactionStateException:
    Existing transaction found for transaction marked with 
    propagation 'never'

    at org.springframework.transaction.interceptor.
    TransactionAspectSupport.invokeWithinTransaction(
        TransactionAspectSupport.java:397)

Лучшие практики

✅ Используйте NEVER для явного указания, что метод не должен быть в транзакции
✅ Это помогает предотвратить ошибки в многоуровневой архитектуре
✅ Комбинируйте с REQUIRES_NEW для асинхронного логирования

⚠️ Не переусложняйте — если метод просто читает данные, используйте readOnly=true вместо NEVER
⚠️ NEVER слишком строгий — рассмотрите SUPPORTS или NOT_SUPPORTED если нужна гибкость

Альтернатива: Async логирование

Вместо NEVER часто лучше использовать асинхронное логирование:

@Service
public class AuditService {
    
    @Async
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logAsync(String message) {
        // Выполняется в отдельном потоке, отдельная транзакция
        auditRepository.save(new AuditLog(message));
    }
}

@Service
public class PaymentService {
    
    @Transactional
    public void processPayment(Long orderId) {
        // Основная работа
        paymentRepository.save(new Payment(orderId));
        
        // Логирование в отдельной транзакции (асинхронно)
        auditService.logAsync("Payment processed: " + orderId);
    }
}

Заключение

Propagation.NEVER — это явное указание на то, что метод НЕ должен выполняться в контексте транзакции. Используйте его для защиты операций, которые логически не должны быть частью основной транзакции (логирование, кэширование, утилиты). Это помогает избежать неправильного нложения транзакций и делает код более безопасным и понятным.