LazyInitializationException
LazyInitializationException — попытка обратиться к “ленивой” (LAZY) связи после того, как сессия Hibernate уже закрылась. Одна из самых частых ошибок у новичков в JPA.
@Service
class UserService {
User getUser(Long id) {
return userRepository.findById(id).get(); // сессия закрывается тут же, после метода
}
}
// в другом месте:
User user = userService.getUser(1L);
user.getOrders().size(); // 💥 LazyInitializationException — сессия уже закрыта!
orders помечены LAZY (см. тему «FetchType, Cascade, состояния entity») — реальный SQL-запрос за ними выполняется только в момент обращения, а к этому моменту сессия (которая умеет ходить в базу) уже закрыта.
Копнуть глубже
Три способа решить проблему:
- Загрузить нужные данные заранее, пока сессия ещё открыта (например, через
JOIN FETCHв JPQL):@Query("SELECT u FROM User u JOIN FETCH u.orders WHERE u.id = :id") Optional<User> findByIdWithOrders(@Param("id") Long id); @Transactionalна сервисном методе — держит сессию открытой на весь метод, включая обращение к ленивым полям внутри него.- DTO-проекция — сразу выбрать только нужные поля плоским запросом, не работая с ленивыми связями объекта вообще.
Почему просто “сделать всё EAGER” — плохое решение. Это убирает симптом, но не причину: EAGER означает подгрузку всегда, даже когда данные не нужны — это бьёт по производительности на каждом запросе, где загружается объект, независимо от того, нужны ли связанные данные в конкретном случае. Лучше осознанно решать в каждом сценарии, что подгружать.
• хотя бы один из трёх способов её избежать и почему "сделать всё EAGER" — плохая идея (если дошёл до 2-го слоя).