Gymterview
junior

Что такое FetchType.LAZY и FetchType.EAGER?

FetchType определяет, когда Hibernate загружает связанные сущности из БД. EAGER загружает сразу вместе с основной сущностью, LAZY — только при первом обращении.

Поведение

Пример
// EAGER — загрузка сразу
@ManyToOne(fetch = FetchType.EAGER)
private Department department;
// При загрузке User → сразу загружается Department

// LAZY — загрузка по требованию
@ManyToOne(fetch = FetchType.LAZY)
private Department department;
// При загрузке User → department НЕ загружается
// department загрузится при вызове user.getDepartment().getName()

Значения по умолчанию

Тип связи FetchType по умолчанию
@ManyToOne EAGER
@OneToOne EAGER
@OneToMany LAZY
@ManyToMany LAZY

Как LAZY работает технически

Hibernate подставляет вместо реальной сущности прокси-объект (наследник сущности, сгенерированный через Byte Buddy). При первом вызове метода прокси выполняет SQL-запрос:

Пример
User user = session.find(User.class, 1L);
// user.department — это прокси (не настоящий Department)

String deptName = user.getDepartment().getName();
// ↑ Здесь прокси выполняет SQL: SELECT * FROM departments WHERE id = ?

Важное

  • Всегда явно указывайте FetchType.LAZY для @ManyToOne и @OneToOne (по умолчанию EAGER!)
  • LAZY — это подсказка, не требование; Hibernate может загрузить eager при необходимости
  • LAZY работает через прокси-объекты — вызов метода на прокси инициирует SQL-запрос
  • LAZY требует открытой Session — обращение к прокси после закрытия Session → LazyInitializationException

Частые ошибки

  • Оставлять @ManyToOne без явного fetch = LAZY — по умолчанию EAGER, загрузка всех связей каскадно
  • EAGER для коллекций — загрузка List<Order> при каждом чтении User — катастрофа для производительности
  • Множественные EAGER-коллекции — Hibernate не может выполнить один запрос для двух EAGER-коллекций; получается Cartesian product или MultipleBagFetchException
  • Проверка == null для LAZY-прокси — прокси никогда не null (даже если записи в БД нет); используйте Hibernate.isInitialized()

Как используется в 2026

  • Правило: всё LAZY, загружать через JOIN FETCH или EntityGraph по необходимости
  • Spring Boot spring.jpa.open-in-view=false — отключение OSIV заставляет явно загружать данные в сервисном слое
  • Hibernate 6 bytecode enhancement — позволяет LAZY для @Basic полей (не загружать тяжёлые BLOB/CLOB до обращения)

На собеседовании: правило номер один — всё LAZY. Покажите, что знаете дефолты: @ManyToOne и @OneToOne по умолчанию EAGER. Объясните, как работает прокси и что произойдёт при обращении к LAZY-связи после закрытия Session. Упомяните JOIN FETCH как основной способ загрузки нужных данных.