Разберите LazyInitializationException, который летит при отрисовке сущности в контроллере
Эндпоинт ниже возвращает заказ вместе с его позициями. Вызов репозитория проходит, но запрос падает с org.hibernate.LazyInitializationException: could not initialize proxy - no Session ровно в момент сериализации ответа. Тот же код проходит внутри @Transactional-теста.
Ограничения:
itemsдолжны остатьсяLAZY— другие эндпоинты грузят заказ без позиций- не включайте
spring.jpa.open-in-view
@Entity
class Order {
@Id Long id;
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
List<OrderItem> items = new ArrayList<>();
}
@Service
class OrderService {
@Transactional(readOnly = true)
public Order findOrder(Long id) {
return repo.findById(id).orElseThrow();
}
}
@RestController
class OrderController {
@GetMapping("/orders/{id}")
public Order get(@PathVariable Long id) {
return service.findOrder(id); // ← сериализация трогает order.getItems()
}
}
Определите причину.
items — ленивый прокси, который может инициализироваться, только пока открыт породивший его persistence context. @Transactional заканчивается на выходе из findOrder, сессия закрывается, и сериализатор трогает прокси уже вне неё — отсюда no Session. Тест проходит лишь потому, что его собственный @Transactional держит сессию открытой. Лечится загрузкой нужного внутри транзакции (JOIN FETCH, @EntityGraph) и возвратом DTO.
- ✗Винить сам маппинг
LAZYвместо закрытого persistence context - ✗Включить
open-in-viewи считать это исправлением — оно прячет N+1 селектов за рендерингом - ✗Возвращать из контроллера отсоединённую сущность вместо DTO
- →Почему
Hibernate.initialize(order.getItems())вне транзакции тоже упадёт? - →Почему
open-in-view— плохое умолчание для REST-сервиса?
Что происходит
repo.findById(id) возвращает Order, у которого items — не ArrayList, а ленивый прокси коллекции (PersistentBag). Он хранит ссылку на свою сессию и разворачивается только при первом обращении. Транзакция открыта аннотацией @Transactional на findOrder и закрывается ровно на выходе из метода — вместе с ней закрывается и persistence context.
Контроллер получает уже отсоединённую (detached) сущность. Джексон обходит её поля, дёргает getItems(), прокси пытается сходить в свою сессию — а её нет:
org.hibernate.LazyInitializationException: could not initialize proxy
[Order.items#42] - no Session
at org.hibernate.collection.spi.AbstractPersistentCollection.throwLazyInitializationException
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(...)
Тест проходит потому, что @Transactional на тестовом методе держит сессию открытой до конца теста — граница транзакции там просто шире, а не «код другой».
Исправление
public interface OrderRepo extends JpaRepository<Order, Long> {
@EntityGraph(attributePaths = "items") // грузим позиции тем же запросом
Optional<Order> findWithItemsById(Long id);
}
@Service
class OrderService {
@Transactional(readOnly = true)
public OrderDto findOrder(Long id) {
Order order = repo.findWithItemsById(id).orElseThrow();
return OrderDto.from(order); // ✅ маппинг в DTO ВНУТРИ транзакции
}
}
Другие эндпоинты по-прежнему зовут обычный findById и не платят за позиции — LAZY остаётся на месте.
❌ spring.jpa.open-in-view=true растянет сессию на всё время рендеринга и исключение уберёт, но не проблему: ленивые обращения переедут в слой представления, где родят те же N+1 запросов, и соединение будет удерживаться до последнего байта ответа.