DEV Community

Cover image for Hibernate Zoo: Жадный Гиппопотам и Ленивый Лемур (Lazy vs Eager)
Olga Lugacheva
Olga Lugacheva

Posted on

Hibernate Zoo: Жадный Гиппопотам и Ленивый Лемур (Lazy vs Eager)

Сегодня мы познакомимся с двумя знаменитыми обитателями Hibernate Zoo: Жадным Гиппопотамом (EAGER fetching) и Ленивым Лемуром (LAZY fetching). Но перед тем как погрузиться в их особенности, давайте разберёмся, что означают аннотации @OneToMany, @OneToOne и другие. Эти аннотации — как указатели в зоопарке, определяющие связи между животными. В случае Hibernate они описывают отношения между сущностями в базе данных.

Понимание аннотаций в Hibernate

Hibernate использует аннотации для описания отношений между сущностями.

@OneToOne
Обозначает связь "один к одному", где одна сущность связана ровно с одной другой сущностью.
Пример: У пользователя (User) есть один профиль (Profile).

@OneToMany
Обозначает связь "один ко многим", где одна сущность связана с несколькими другими.
Пример: Один отдел (Department) может содержать нескольких сотрудников (Employees).

@ManyToOne
Обратное отношение к @OneToMany: несколько сущностей связаны с одной.
Пример: Многие заказы (Orders) принадлежат одному клиенту (Customer).

@ManyToMany
Обозначает связь "многие ко многим", где обе стороны могут быть связаны с несколькими сущностями.
Пример: Студенты (Students) могут посещать много курсов (Courses), а каждый курс может иметь много студентов.

Эти аннотации описывают, как сущности связаны, но не указывают, когда загружать данные. За это отвечают стратегии загрузки данных: EAGER (жадная) и LAZY (ленивая).

Жадный Гиппопотам (EAGER Fetching)

Жадный Гиппопотам — настоящий трудоголик. Увидев запрос к базе данных, он тут же спешит загрузить все связанные данные, даже если вы не планируете их использовать.

hippo

Особенности Жадного Гиппопотама:

@OneToMany(fetch = FetchType.EAGER)
private List<Food> foodList;

Enter fullscreen mode Exit fullscreen mode

В этой ситуации он выполнит один сложный запрос с JOIN, чтобы сразу принести вам и объект, и всё, что к нему относится:

SELECT a.*, f.* 
FROM animal a 
LEFT JOIN food f ON a.id = f.animal_id 
WHERE a.id = 1;

Enter fullscreen mode Exit fullscreen mode

Преимущества:
Гиппопотам идеально подходит, когда вам всегда нужны все связанные данные. Например, вы выводите список животных вместе с их привычками, и дополнительный запрос не нужен.

Недостатки:
Жадность может привести к перегрузке базы данных. Гиппопотам загружает всё, даже если это не нужно, что особенно болезненно при множественных связях. Вы также рискуете столкнуться с N+1 проблемой, если будете запрашивать данные в цикле.

Ленивый Лемур (LAZY Fetching)

Ленивый Лемур, напротив, работает только по запросу. Он загружает связанные данные только тогда, когда вы к ним обращаетесь.

lem
Особенности Ленивого Лемура:
Стратегия:

@OneToMany(fetch = FetchType.LAZY)
private List<Food> foodList;

Enter fullscreen mode Exit fullscreen mode

При запросе объекта он подгрузит только его данные:

SELECT * FROM animal WHERE id = 1;

Enter fullscreen mode Exit fullscreen mode

А связанные сущности загрузит позже, при первом обращении: animal.getFoodList();

SELECT * FROM food WHERE animal_id = 1;
Enter fullscreen mode Exit fullscreen mode

Преимущества:
Ленивость экономит ресурсы. Вы запрашиваете ровно столько данных, сколько нужно, когда это нужно.

Недостатки:
Если забыть про ленивую стратегию и попытаться получить данные вне сессии, Hibernate выбросит исключение LazyInitializationException.

Секреты поведения по умолчанию

Если вы не указали стратегию загрузки, Hibernate сам решит, кто из животных возьмёт на себя работу. Но у него свои правила.

Жадный по умолчанию (EAGER)
Для связей @OneToOne и @ManyToOne Hibernate выбирает жадную загрузку. Это значит, что связанные сущности будут загружены сразу.

@Entity
class Animal {
    @OneToOne
    private Habitat habitat;
}

Enter fullscreen mode Exit fullscreen mode
SELECT a.*, h.* 
FROM animal a 
LEFT JOIN habitat h ON a.habitat_id = h.id 
WHERE a.id = 1;

Enter fullscreen mode Exit fullscreen mode

Почему?
Считается, что такие связи обычно важны для приложения, поэтому их загрузка заранее упрощает работу.

Ленивый по умолчанию (LAZY)
Для связей @OneToMany и @ManyToMany Hibernate выбирает ленивую загрузку.

Пример:

@Entity
class Animal {
    @OneToMany
    private List<Food> foodList;
}

Enter fullscreen mode Exit fullscreen mode

При запросе Animal данные из Food не загружаются сразу:

SELECT * FROM animal WHERE id = 1;

Enter fullscreen mode Exit fullscreen mode

Но как только вы обращаетесь к foodList, выполняется отдельный запрос:

SELECT * FROM food WHERE animal_id = 1;

Enter fullscreen mode Exit fullscreen mode

Гиппопотам или Лемур: Кого выбрать?

Характеристика Жадный Гиппопотам (EAGER) Ленивый Лемур (LAZY)
Когда использовать Когда данные всегда нужны Когда данные могут не понадобиться
Количество запросов Один, но большой Несколько, но маленьких
Проблемы Избыточная загрузка LazyInitializationException

Совет:
Лучше всегда явно указывать стратегию - это избавит вас от сюрпризов.

Заключение

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

EAGER для @OneToOne и @ManyToOne.
LAZY для @OneToMany и @ManyToMany.
Не забывайте приручать своих животных: выбирайте подходящую стратегию, чтобы ваш Hibernate Zoo работал эффективно. Удачного кодинга! 🐾

Top comments (0)