Gymterview
middle

Что такое LazyInitializationException и как его избежать?

LazyInitializationException — исключение, возникающее при обращении к LAZY-связи после закрытия Session (Persistence Context).

Пример
@Transactional
public User getUser(Long id) {
    return userRepository.findById(id).orElseThrow();
    // Session закрывается при выходе из @Transactional
}

// В контроллере:
User user = userService.getUser(1L);
user.getOrders().size(); // LazyInitializationException!
// Session уже закрыта, прокси не может загрузить данные

Способы решения

  1. JOIN FETCH — загрузить связь в запросе:
Пример
@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.id = :id")
Optional<User> findByIdWithOrders(@Param("id") Long id);
  1. @EntityGraph — декларативная загрузка:
Пример
@EntityGraph(attributePaths = {"orders"})
Optional<User> findById(Long id);
  1. DTO-проекция — вернуть только нужные данные:
Пример
@Transactional(readOnly = true)
public UserDto getUser(Long id) {
    User user = userRepository.findByIdWithOrders(id).orElseThrow();
    return new UserDto(user.getName(), user.getOrders().stream()
        .map(OrderDto::from).toList());
}
  1. Инициализация внутри транзакции:
Пример
@Transactional(readOnly = true)
public User getUser(Long id) {
    User user = userRepository.findById(id).orElseThrow();
    Hibernate.initialize(user.getOrders()); // принудительная загрузка
    return user;
}
  1. Open Session in View (OSIV) — anti-pattern:
Пример
spring.jpa.open-in-view=true  # по умолчанию в Spring Boot — Session открыта до рендеринга View

Важное

  • LazyInitializationException = обращение к LAZY-прокси после закрытия Session
  • Лучшее решение: JOIN FETCH или @EntityGraph — загрузка нужных данных в сервисном слое
  • OSIV (spring.jpa.open-in-view=true) решает проблему, но создаёт новые: долгие транзакции, непредсказуемые запросы
  • Рекомендация: open-in-view=false + явная загрузка данных в сервисах

Частые ошибки

  • Полагаться на OSIV — маскирует проблему; запросы к БД выполняются из контроллера/view, усложняя отладку
  • Hibernate.initialize() для больших коллекций — загружает все элементы в память; для 10K записей лучше пагинация
  • Возвращать Entity из контроллера — при сериализации в JSON Jackson обращается к LAZY-полям → исключение; используйте DTO

Как используется в 2026

  • Стандартный подход: open-in-view=false + DTO-проекции + JOIN FETCH
  • Spring Boot предупреждает о включённом OSIV в логах при старте
  • В REST API — всегда DTO, никогда Entity в ответе; это решает и LazyInitializationException, и проблемы безопасности

На собеседовании: причина LazyInitializationException — обращение к LAZY-прокси после закрытия Session. Решения по приоритету: JOIN FETCH, @EntityGraph, DTO-проекция. OSIV — антипаттерн, хотя включён по умолчанию. Обязательно упомяните, что в REST API нужно возвращать DTO, не Entity.