Что такое FetchType в JPA/Hibernate?

Ответ

FetchType — это перечисление в JPA, определяющее стратегию загрузки связанных сущностей. Оно управляет тем, когда Hibernate загружает данные из базы.

Два типа загрузки:

  1. FetchType.EAGER — связанная сущность загружается немедленно вместе с владельцем.

    • По умолчанию для @ManyToOne и @OneToOne.
    • Риск: Может привести к проблеме N+1 запроса и избыточному потреблению памяти, если загружается много данных.
  2. FetchType.LAZY — связанная сущность загружается только при первом обращении к ней.

    • По умолчанию для @OneToMany и @ManyToMany.
    • Риск: Может вызвать LazyInitializationException, если доступ происходит вне активной сессии Hibernate (например, в слое представления после закрытия транзакции).

Пример использования:

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

    // Покупатель загрузится только при вызове order.getCustomer()
    @ManyToOne(fetch = FetchType.LAZY)
    private Customer customer;

    // Все позиции заказа загрузятся сразу при загрузке Order
    @OneToMany(fetch = FetchType.EAGER, mappedBy = "order")
    private List<OrderItem> items;
}

Рекомендации:

  • Предпочитайте LAZY загрузку по умолчанию для оптимизации производительности.
  • Используйте EAGER осознанно, только когда связанные данные нужны практически всегда.
  • Для решения проблемы N+1 с LAZY используйте JOIN FETCH в JPQL или @EntityGraph.

Ответ 18+ 🔞

А, ну вот, опять про эту загрузку, блядь, FetchType! Это ж как тот выбор — заварить чай сейчас или потом, когда пить захочется. Только тут не чай, а данные из базы, и если накосячить — можно такую лапшу из запросов получить, что мало не покажется.

Короче, есть две стратегии, и они как день и ночь, ёпта.

FetchType.EAGER — это как жадный мужик в столовой. Заказывает себе всё и сразу: первое, второе, компот и три куска хлеба вприкуску, блядь. Сущность-владелец грузится — и тут же, волшебным пендюлем, тащит за собой все связанные данные. По умолчанию так ведут себя @ManyToOne и @OneToOne. А потом сидит с полными подносами и думает: «И нахуя мне столько?» Потому что если таких жадных много, то база начнёт орать, как резаная, а память — накрываться медным тазом. Это и есть та самая «проблема N+1», когда вместо одного красивого запроса получается овердохуища маленьких и тупых.

FetchType.LAZY — это уже хитрая жопа. «Загружу, когда приспичит!» Грузится только основная сущность, а все её связи — как спящие собаки. Полез к ним (например, вызвал order.getCustomer()) — вот тогда они и проснутся и сбегают в базу. По умолчанию так себя ведут @OneToMany и @ManyToMany. Экономия — огонь. Но подвох есть, и он грозный — LazyInitializationException. Представь: достал ты заказ в сервисе, сессия с базой закрылась, а потом в контроллере или шаблоне решил посмотреть, кто там покупатель. А покупатель-то не загружен! И тут Hibernate тебе с улыбкой: «Нет уж, дружок, сессия закрыта, иди нахуй». И всё, пиздец, ошибка.

Вот смотри, как это в коде выглядит, тут всё без изменений:

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

    // Покупатель — тот самый ленивый пёс. Загрузится только если его позвать по имени.
    @ManyToOne(fetch = FetchType.LAZY)
    private Customer customer;

    // А вот позиции заказа — те самые жадные обжоры. Загрузятся все сразу, как только Order появится.
    @OneToMany(fetch = FetchType.EAGER, mappedBy = "order")
    private List<OrderItem> items;
}

Так что, блядь, запомни главное правило, чтобы не было мучительно больно:

  1. По умолчанию везде ставь LAZY. Это как золотой стандарт, чтобы не выстрелить себе в ногу.
  2. EAGER — только если ты на 146% уверен, что эти связанные данные нужны всегда и везде. А так почти никогда не уверен.
  3. Если выбрал LAZY, но в конкретном месте нужно всё и сразу — используй JOIN FETCH в запросе или эти ваши @EntityGraph. Они как раз для того, чтобы разово, по делу, нажраться данных без последствий.

Вот и вся философия. Не усложняй, и будет тебе счастье, а не исключения в рот тебе чих-пых.