FetchType в Hibernate

FetchType — подсказка провайдеру о моменте загрузки связи.

  • LAZY — связь загружается при первом использовании;
  • EAGER — связь должна быть доступна после загрузки владельца, но способ получения не задан.

Стандартные значения по умолчанию:

СвязьПо умолчанию
@ManyToOne, @OneToOneEAGER
@OneToMany, @ManyToManyLAZY

На практике связи обычно объявляют LAZY и задают fetch-план на уровне конкретного запроса.

FetchType не равен SQL-стратегии

EAGER не гарантирует один JOIN: провайдер может выполнить дополнительные запросы. LAZY также не гарантирует прокси для каждого случая. То, как получить данные, выбирают через:

  • join fetch в JPQL;
  • entity graph;
  • DTO-проекцию;
  • batch fetching;
  • явную инициализацию внутри транзакции.
select distinct o
from Order o
left join fetch o.lines
where o.id = :id

Два fetch join по коллекциям могут создать декартово размножение. Пагинация по коллекционному fetch join также часто выполняется неверно или в памяти.

Типичные сбои

  • Кейс N plus 1 при обходе иерархии;
  • LazyInitializationException после закрытия контекста;
  • огромный граф из-за EAGER;
  • дубли корневых объектов при join;
  • неожиданная сериализация всего графа и циклических связей.

Вопросы для повторения

  1. EAGER означает один запрос? Нет.
  2. Почему LAZY не решает N+1? Каждое обращение к связи всё равно может вызвать отдельный SQL.
  3. Где загружать нужный граф? В транзакционном сервисе запросом, соответствующим use case.
  4. Можно ли лениво загружать после закрытия контекста? Нет без повторного присоединения или специального анти-паттерна.
  5. Чем DTO полезнее сущности для чтения? Он выбирает ровно нужные поля и не несёт ленивого графа.

Связано: Persistence Context, Entity Graph, Проблема N plus 1 Select Hibernate.