Ответ
JPA (и его реализация Hibernate) организует отложенную загрузку через механизм динамических прокси-объектов и интерсепторов (перехватчиков).
Принцип работы:
- Проксирование: Когда вы запрашиваете сущность или связь с
FetchType.LAZY, Hibernate возвращает не реальный объект, а его прокси — динамически сгенерированный подкласс, который переопределяет методы доступа к полям. - Ловушка (Interceptor): При первом обращении к любому методу прокси-объекта (например,
getId()илиgetItems()), срабатывает перехватчик, который инициирует загрузку реальных данных из БД. - Инициализация: После загрузки данных прокси "делегирует" вызовы методов реальному, полностью загруженному объекту.
Пример и важные детали:
@Entity
public class Order {
@Id
private Long id;
// LAZY — коллекция будет загружена только при первом обращении
@OneToMany(fetch = FetchType.LAZY, mappedBy = "order")
private List<OrderItem> items; // Прокси-коллекция
}
// В коде сервиса:
Order order = em.find(Order.class, 1L); // Загружен только Order
// ...
order.getItems().size(); // ТРИГГЕР: Здесь выполняется SQL для загрузки items
Ключевые моменты:
- Контекст сессии (Persistence Context): Ленивая загрузка работает только пока открыта сессия/EntityManager. После его закрытия обращение к непроинициализированному прокси вызовет
LazyInitializationException. - Типы прокси: Для классов используются прокси на основе CGLIB/Javassist, для коллекций — специальные обертки (PersistentBag, PersistentSet).
- Практика: Часто используют
JOIN FETCHв JPQL или@EntityGraphдля предварительной (eager) загрузки нужных связей в одном запросе, избегая проблемы N+1.