Ответ
Разница заключается в стратегии загрузки связанных сущностей.
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 проблемы.
Ответ 18+ 🔞
А, ну это же классика, блядь! Вечная тема, про которую каждый второй джуниор мозги себе выносит, пока не поймёт, в чём подвох. Слушай сюда, сейчас разжую, как есть.
Вот представь, ты заказываешь пиццу. FetchType — это как раз твоя стратегия по её получению.
EAGER(жадный) — это когда ты, сука, такой нетерпеливый, заказываешь пиццу и сразу же, в одном заказе, требуешь, чтобы тебе привезли ещё и колу, и салат, и соусы на всю овердохуищу, даже если ты их, возможно, и не будешь есть. Курьер приезжает с одной здоровенной, ебучей коробкой, в которой всё на свете. Вроде всё сразу есть, но тащить это пиздец как тяжело, и половина может оказаться ненужной.LAZY(ленивый) — это нормальная, адекватная стратегия. Ты заказываешь только пиццу. Привозят. Ты её начинаешь жрать, и тут тебя осеняет: «А хули я, блядь, без колы? Горло сухое!». Только тогда ты звонишь и заказываешь колу отдельным заказом. Экономишь силы и деньги, если кола не понадобится.
Как это в коде выглядит, ёпта:
@Entity
public class Order {
@Id
private Long id;
// EAGER загрузка (дефолт для @ManyToOne) - закажешь пиццу, клиента (Customer) прицепят сразу, хош-не-хош
@ManyToOne(fetch = FetchType.EAGER)
private Customer customer;
// LAZY загрузка (дефолт для @OneToMany) - позиции заказа (OrderItem) подгрузятся только если полезешь в них пальцами
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
private List<OrderItem> items;
}
Теперь про плюсы и минусы, тут собака, сука, порылась:
-
LAZY(Ленивая, она же нормальная):- Плюсы: Начальная загрузка — огонь, быстро. Не тащишь в память тонну ебучего хлама, который, возможно, и не понадобится. Здравый смысл, блядь!
- Минусы: А вот тут засада. Если ты в цикле начнёшь обходить сто заказов и для каждого лезть в его
items, то Hibernate, тупая мартышлюшка, сделает на каждый заказ отдельный запрос. Это и есть та самая проблема N+1 SELECT, от которой у всех волосы дыбом. И ещё: обращаться к ленивым полям можно только пока сессия жива, а то получишьLazyInitializationException— мол, «извини, дружок, пиццерия закрылась, колу теперь не закажешь».
-
EAGER(Жадная, она же проблемная):- Плюсы: Данные всегда при тебе, как преданный пёс. Никаких исключений при обращении.
- Минусы: А производительность, блядь? Ты один раз запросил список из 100 заказов, а Hibernate, такой ретивый, в одном запросе попытался притащить тебе и все заказы, и всех клиентов, и все позиции к каждому заказу, и их товары... Вжух! И приложение легло, накрылось медным тазом от такого объёма хуйни. Это как пытаться за один присест съесть весь склад пиццерии.
Так что же делать, ёбана? Best Practice, который все узнают кровью:
Ставь везде LAZY по умолчанию, как будто это твоя религия. Это безопасно и правильно.
А когда тебе действительно нужны связанные данные (например, ты на странице «Детали заказа» и точно знаешь, что будешь показывать и заказ, и все его позиции), тогда не будь мудаком, не полагайся на жадную загрузку.
Используй JOIN FETCH в своём JPQL-запросе:
SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id
Вот это, блядь, серебряная пуля. Ты явно говоришь Хибернейту: «Слушай, пацан, дай мне вот этот конкретный заказ, и заодно, одним запросом, прихвати все его позиции. Не городи N+1, будь умником». И он сделает один красивый, эффективный JOIN, и проблема снимается.
Короче, запомни: EAGER — это как предоплата за всё, что есть в меню, на случай если проголодаешься. LAZY с JOIN FETCH — это разумный заказ по списку, когда точно знаешь, что будешь жрать. Не усложняй, живи проще.