Какие проблемы возникают без @Transactional при работе с Spring Data JPA репозиторием?

«Какие проблемы возникают без @Transactional при работе с Spring Data JPA репозиторием?» — вопрос из категории Spring, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Основные проблемы:

  1. Раздельные транзакции для каждой операции

    // Без @Transactional на методе сервиса
    public void processOrder(Order order) {
       orderRepository.save(order);           // Транзакция 1
       inventoryRepository.reduceStock(order); // Транзакция 2
       // Если reduceStock() упадёт, order уже сохранён!
    }
  2. LazyInitializationException при загрузке ленивых ассоциаций

    @Entity
    public class User {
       @OneToMany(fetch = FetchType.LAZY)
       private List<Order> orders;  // Ленивая коллекция
    }
    
    public void printOrders(Long userId) {
       User user = userRepository.findById(userId).orElseThrow();
       // Транзакция уже закрыта (репозиторий сам открывал)
    
       for (Order order : user.getOrders()) {  // LazyInitializationException!
           System.out.println(order);
       }
    }
  3. Отсутствие управления уровнем изоляции и таймаутами

Решение – использование @Transactional:

@Service
public class OrderService {

    @Transactional  // Открывает транзакцию перед методом, закрывает после
    public void processOrder(OrderDto dto) {
        // ВСЕ операции в одной транзакции
        Order order = orderRepository.save(createOrder(dto));
        inventoryRepository.updateStock(order.getItems());
        auditRepository.logOrder(order);

        // При любом RuntimeException/Error произойдёт rollback всей транзакции
    }

    @Transactional(readOnly = true)  // Оптимизация для чтения
    public User getUserWithOrders(Long userId) {
        User user = userRepository.findById(userId).orElseThrow();

        // Ленивая загрузка работает, т.к. транзакция активна
        user.getOrders().size();  // Инициализация коллекции
        user.getOrders().forEach(System.out::println);

        return user;
    }
}

Рекомендации по использованию:

  • Размещайте @Transactional на уровне сервиса, а не репозитория
  • Используйте readOnly = true для методов, которые только читают данные
  • Указывайте rollbackFor для кастомных исключений
  • Избегайте вызовов @Transactional методов изнутри того же класса (проблема proxy)