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 уже закрыта, прокси не может загрузить данные
Способы решения
- JOIN FETCH — загрузить связь в запросе:
Пример
@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.id = :id")
Optional<User> findByIdWithOrders(@Param("id") Long id);
- @EntityGraph — декларативная загрузка:
Пример
@EntityGraph(attributePaths = {"orders"})
Optional<User> findById(Long id);
- 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());
}
- Инициализация внутри транзакции:
Пример
@Transactional(readOnly = true)
public User getUser(Long id) {
User user = userRepository.findById(id).orElseThrow();
Hibernate.initialize(user.getOrders()); // принудительная загрузка
return user;
}
- 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.