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

Какие проблемы возникают при использовании аннотации @Lazy без @Transactional

2.7 Senior🔥 131 комментариев
#ORM и Hibernate#Spring Framework

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

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

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

Проблемы использования @Lazy без @Transactional

@Lazy аннотация в Spring позволяет отложить инициализацию бина до момента его первого использования. Однако использование @Lazy без @Transactional может привести к серьёзным проблемам, особенно при работе с Hibernate и JPA.

1. LazyInitializationException

Проблема: Если бин с @Lazy инициализируется вне транзакции и пытается доступить к ленивым связям объектов, произойдёт исключение.

// Плохо - @Lazy без @Transactional
@Component
@Lazy
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    public User getUser(Long id) {
        return userRepository.findById(id); // Сессия закрывается здесь
    }
}

@RestController
public class UserController {
    @Autowired
    private UserService userService;
    
    @GetMapping("/users/{id}")
    public User getUser(@PathVariable Long id) {
        User user = userService.getUser(id);
        
        // LazyInitializationException! Сессия уже закрыта
        return user.getOrders(); // Ошибка - Hibernate session is closed
    }
}

// Правильно - добавить @Transactional
@Component
@Lazy
public class UserService {
    @Transactional // Сессия остаётся открытой
    public User getUser(Long id) {
        return userRepository.findById(id);
    }
}

2. N+1 проблема запросов

Проблема: Без явной стратегии загрузки и транзакции, каждое обращение к ленивой связи вызывает новый запрос.

// Плохо - N+1 запросы
@Component
@Lazy
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    
    public List<Order> getOrdersWithCustomers(int page) {
        List<Order> orders = orderRepository.findAll(PageRequest.of(page, 20)).getContent();
        return orders; // 1 запрос
    }
}

@RestController
public class OrderController {
    @Autowired
    private OrderService orderService;
    
    @GetMapping("/orders")
    public List<OrderDTO> getOrders(@RequestParam int page) {
        List<Order> orders = orderService.getOrders(page); // 1 запрос
        
        // N+1 - для каждого заказа идёт запрос к клиенту
        return orders.stream()
            .map(o -> new OrderDTO(
                o.getId(),
                o.getCustomer().getName() // N дополнительных запросов!
            ))
            .collect(Collectors.toList());
    }
}

// Правильно - использовать FETCH JOIN и @Transactional
@Component
@Lazy
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;
    
    @Transactional(readOnly = true) //읽ение данных
    public List<Order> getOrdersWithCustomers(int page) {
        // FETCH JOIN загружает customer в одном запросе
        return orderRepository.findAllWithCustomer(
            PageRequest.of(page, 20)
        ).getContent();
    }
}

// В репозитории
public interface OrderRepository extends JpaRepository<Order, Long> {
    @Query("SELECT o FROM Order o " +
           "LEFT JOIN FETCH o.customer " +
           "WHERE o.id IN " +
           "(SELECT o2.id FROM Order o2 ORDER BY o2.id DESC LIMIT :limit OFFSET :offset)")
    Page<Order> findAllWithCustomer(Pageable pageable);
}

3. Проблемы с многопоточностью

Проблема: Ленивая инициализация в разных потоках может привести к race conditions.

// Плохо - @Lazy без @Transactional в многопоточной среде
@Component
@Lazy
public class DataService {
    @Autowired
    private DataRepository repository;
    
    public Data getData(Long id) {
        return repository.findById(id);
        // Сессия закрывается после метода
    }
}

@Service
public class AsyncProcessor {
    @Autowired
    private DataService dataService;
    
    @Async
    public void processData(Long id) {
        Data data = dataService.getData(id);
        
        // Race condition! В другом потоке сессия другая
        executor.execute(() -> {
            data.getRelations(); // LazyInitializationException!
        });
    }
}

// Правильно - использовать @Transactional с propagation
@Component
@Lazy
public class DataService {
    @Autowired
    private DataRepository repository;
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Data getDataWithRelations(Long id) {
        Data data = repository.findById(id);
        
        // Загрузить всё в пределах одной транзакции
        data.getRelations().size(); // Инициализировать здесь
        
        return data;
    }
}

4. Утечки памяти и недопроизводительность

Проблема: Без правильного управления жизненным циклом Hibernate сессии могут оставаться открытыми.

// Плохо - сессия может не закрыться
@Component
@Lazy
public class ReportService {
    @Autowired
    private ReportRepository reportRepository;
    
    public Report generateReport(Long id) {
        Report report = reportRepository.findById(id);
        // Сессия не закрыта явно, может оставаться открытой
        return report;
    }
}

// Результат - утечка памяти и "too many connections" ошибки

// Правильно - @Transactional управляет сессией
@Component
@Lazy
public class ReportService {
    @Autowired
    private ReportRepository reportRepository;
    
    @Transactional(readOnly = true)
    public Report generateReport(Long id) {
        Report report = reportRepository.findById(id);
        // Сессия автоматически закроется в конце метода
        return report;
    }
}

5. Проблемы с Entity Graph и Spring Data

Проблема: Entity Graph не будет работать правильно без @Transactional.

// Плохо - Entity Graph без @Transactional
@Component
@Lazy
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    public User getUserWithPosts(Long id) {
        // Entity Graph не сработает без Transactional контекста
        return userRepository.findByIdWithPosts(id);
    }
}

public interface UserRepository extends JpaRepository<User, Long> {
    @EntityGraph(attributePaths = {"posts", "comments"})
    User findByIdWithPosts(Long id); // Сессия закроется сразу
}

// Правильно
@Component
@Lazy
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    @Transactional(readOnly = true)
    public User getUserWithPosts(Long id) {
        // Entity Graph будет работать в контексте транзакции
        return userRepository.findByIdWithPosts(id);
    }
}

6. Непредсказуемое поведение при исключениях

Проблема: Без @Transactional сложно понять, откатится ли состояние при ошибке.

// Плохо - неопределённое поведение
@Component
@Lazy
public class AccountService {
    @Autowired
    private AccountRepository accountRepository;
    
    public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
        Account from = accountRepository.findById(fromId);
        Account to = accountRepository.findById(toId);
        
        from.debit(amount);
        to.credit(amount);
        // Если здесь error - состояние не откатится
        accountRepository.saveAll(Arrays.asList(from, to));
    }
}

// Правильно - @Transactional гарантирует atomicity
@Component
@Lazy
public class AccountService {
    @Autowired
    private AccountRepository accountRepository;
    
    @Transactional // Atomicity гарантирована
    public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
        Account from = accountRepository.findById(fromId);
        Account to = accountRepository.findById(toId);
        
        from.debit(amount);
        to.credit(amount);
        // При любой ошибке - полный откат
        accountRepository.saveAll(Arrays.asList(from, to));
    }
}

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

1. Всегда используйте @Transactional с @Lazy:

@Component
@Lazy
public class MyService {
    @Transactional // Обязательно!
    public Result process() {
        // ...
    }
}

2. Используйте readOnly для операций чтения:

@Transactional(readOnly = true) // Оптимизация для чтения
public List<Data> getData() {
    return repository.findAll();
}

3. Явно загружайте ленивые связи:

@EntityGraph(attributePaths = {"orders", "payments"})
User findByIdWithRelations(Long id);

4. Используйте Hibernate.initialize() если необходимо:

User user = repository.findById(id);
Hibernate.initialize(user.getOrders()); // Загрузить явно

5. Профилируйте запросы:

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL10Dialect

Заключение

@Lazy без @Transactional создаёт множество проблем: LazyInitializationException, N+1 запросы, утечки памяти и непредсказуемое поведение. Всегда используйте @Transactional с @Lazy для правильного управления Hibernate сессией и гарантирования корректного поведения при работе с ленивыми связями.