Как JOIN FETCH в Hibernate влияет на стратегию ленивой загрузки (Lazy Loading)?

«Как JOIN FETCH в Hibernate влияет на стратегию ленивой загрузки (Lazy Loading)?» — вопрос из категории Hibernate, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

JOIN FETCH в JPQL/HQL заставляет Hibernate загружать связанную коллекцию или сущность немедленно (eagerly) в рамках одного SQL-запроса, игнорируя объявленную стратегию FetchType.LAZY.

Цель: Решение проблемы N+1 запроса.

Пример:

@Entity
public class Order {
    @Id
    private Long id;
    @OneToMany(mappedBy = "order", fetch = FetchType.LAZY) // Объявлена LAZY
    private List<OrderItem> items;
}

// Запрос с JOIN FETCH
String jpql = "SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id";
Order order = entityManager.createQuery(jpql, Order.class)
                           .setParameter("id", orderId)
                           .getSingleResult();
// Коллекция `items` уже загружена и доступна вне сессии

Важные ограничения:

  • Пагинация: Использование JOIN FETCH с setFirstResult()/setMaxResults() приведет к QueryException, так как JOIN изменяет результирующий набор строк. Для пагинации связанных коллекций используйте отдельный запрос или @BatchSize.
  • Несколько коллекций: JOIN FETCH более чем одной коллекции в одном запросе может привести к декартову произведению (Cartesian Product). В таких случаях предпочтительнее использовать несколько запросов.
  • Повторяющиеся сущности: Hibernate устраняет дубликаты корневых сущностей в результате, но это может повлиять на производительность.