Когда возникает LazyInitializationException и почему
Что проверяют
Понимаете ли вы, что такое контекст персистентности, и знаете ли правильное лечение вместо обходного.
Как отвечать
Исключение возникает при обращении к ленивой связи, когда контекст персистентности уже закрыт. Hibernate подставляет вместо коллекции или связанной сущности прокси, который догружает данные при первом обращении. Для догрузки нужна открытая сессия. Транзакция закончилась, сессия закрылась, сущность стала отсоединённой, и обращение к непрогруженному полю падает.
Типичный сценарий: сервис с @Transactional вернул сущность, транзакция закрылась на выходе из метода, а контроллер начал сериализовать её в JSON и дошёл до коллекции заказов.
Почему это встречается так часто: коллекции (@OneToMany, @ManyToMany) по умолчанию ленивые, и это правильный выбор по умолчанию, иначе один запрос тянул бы полбазы.
Как чинить правильно:
- проекция в DTO прямо в запросе: наружу отдаётся не сущность, а объект с нужными полями. Это лучшее решение, оно заодно снимает вопрос, что именно сериализуется;
@EntityGraphилиjoin fetchв запросе: связь грузится сразу тем же запросом, когда она точно понадобится;- выполнить всю работу внутри транзакции, а наружу отдать уже готовые данные.
Чего делать не стоит:
- менять связь на
EAGER: исключение исчезнет, но каждый запрос начнёт тянуть лишнее, и появится проблема N+1; - полагаться на открытую сессию в представлении (
open-session-in-view). В Spring Boot она включена по умолчанию и потому маскирует проблему, но держит соединение с базой всю обработку запроса и прячет лишние запросы. Boot специально пишет об этом предупреждение при старте, и в рабочем проекте её выключают.
Где спотыкаются
Ставят EAGER как лекарство и получают проблему N+1.
Отдают сущности наружу вместо объектов передачи данных.
Не знают, что открытая сессия в представлении включена по умолчанию.
Считают, что @Transactional на контроллере это нормальное решение.