В чем разница между FetchType.LAZY и FetchType.EAGER в Hibernate/JPA?

«В чем разница между FetchType.LAZY и FetchType.EAGER в Hibernate/JPA?» — вопрос из категории Hibernate, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Разница заключается в стратегии загрузки связанных сущностей.

  • FetchType.EAGER (жадная): Связанная сущность загружается немедленно, в том же запросе SELECT (или с помощью дополнительного JOIN), что и владеющая сущность.
  • FetchType.LAZY (ленивая): Связанная сущность загружается только при первом явном обращении к этому полю в коде, отдельным запросом.

Настройка:

@Entity
public class Order {
    @Id
    private Long id;

    // EAGER загрузка (по умолчанию для @ManyToOne)
    @ManyToOne(fetch = FetchType.EAGER)
    private Customer customer;

    // LAZY загрузка (по умолчанию для @OneToMany)
    @OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
    private List<OrderItem> items;
}

Плюсы и минусы:

  • LAZY:
    • Плюсы: Повышает производительность начальной загрузки, избегая избыточных JOIN и загрузки ненужных данных.
    • Минусы: Может привести к проблеме N+1 SELECT, если лениво загружать коллекции в цикле. Требует открытой сессии/контекста persistence при обращении, иначе выбрасывается LazyInitializationException.
  • EAGER:
    • Плюсы: Данные доступны сразу, нет риска LazyInitializationException.
    • Минусы: Может серьезно снижать производительность из-за загрузки больших графов объектов одним запросом (например, загрузка всех заказов со всеми позициями).

Best Practice: По умолчанию используйте LAZY для всех ассоциаций. Для случаев, когда связанные данные нужны всегда, используйте JOIN FETCH в запросе (JPQL или Criteria API) для эффективной eager-загрузки только когда это необходимо, избегая N+1 проблемы.