Ответ
FetchType — это перечисление в JPA, определяющее стратегию загрузки связанных сущностей. Оно управляет тем, когда Hibernate загружает данные из базы.
Два типа загрузки:
-
FetchType.EAGER — связанная сущность загружается немедленно вместе с владельцем.
- По умолчанию для
@ManyToOneи@OneToOne. - Риск: Может привести к проблеме N+1 запроса и избыточному потреблению памяти, если загружается много данных.
- По умолчанию для
-
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;
}
Так что, блядь, запомни главное правило, чтобы не было мучительно больно:
- По умолчанию везде ставь LAZY. Это как золотой стандарт, чтобы не выстрелить себе в ногу.
- EAGER — только если ты на 146% уверен, что эти связанные данные нужны всегда и везде. А так почти никогда не уверен.
- Если выбрал LAZY, но в конкретном месте нужно всё и сразу — используй JOIN FETCH в запросе или эти ваши
@EntityGraph. Они как раз для того, чтобы разово, по делу, нажраться данных без последствий.
Вот и вся философия. Не усложняй, и будет тебе счастье, а не исключения в рот тебе чих-пых.