LazyCollectionOption.EXTRA

Открыта

Знакомимся с LazyCollectionOption.EXTRA

Но самый большой интерес представляет значение LazyCollectionOption.EXTRA. Если ты указываешь его как значение аннотации @LazyCollection, то Hibernate будет оттягивать загрузку элементов коллекции как можно дольше.

Если ты попробуешь получить количество элементов коллекции:

User user = session.load(User.class, 1);
List<Comment> comments = user.getComments();
int count = commetns.size();

То для всего этого кода Hibernate выполнит только один запрос:

SELECT COUNT(id) FROM comment WHERE user_id = 1;

Однако если ты хочешь получить один комментарий из коллекции, например, номер 3:

User user = session.load(User.class, 1);
List<Comment> comments = user.getComments();
Comment comment = commetns.get(3);

То возникнет вопрос: а как Hibernate должен узнать, что элемент именно третий, не загружая в память все элементы?

Для решения этой проблемы предлагается сделать в таблице comment дополнительную колонку, которая будет хранить порядковый номер комментария в коллекции комментариев. А также для этого нужна специальная аннотация — @OrderColumn.

Вот как будет выглядеть это решение:

@Entity
@Table(name=”user”)
class User {
   @Column(name=”id”)
   public Integer id;

   @OneToMany(cascade = CascadeType.ALL)
   @LazyCollection(LazyCollectionOption.EXTRA)
   @OrderColumn(name = "order_id")
   public List<Comment> comments;
}

Главное преимущество LazyCollectionOption.EXTRA

Самое сильное преимущество LazyCollectionOption.EXTRA мы наблюдаем, когда указываем его у аннотации @ManyToMany. Возьмем наш старый случай, когда у нас есть Employee, Task, и можно назначать много задач на одного пользователя.

Наши Java-классы при этом выглядят так:

Класс Employee:

@Entity
@Table(name=”employee”)
class Employee {
   @Column(name=”id”)
   public Integer id;

   @ManyToMany(cascade = CascadeType.ALL)
   @JoinTable(name="employee_task",
       	joinColumns=  @JoinColumn(name="employee_id", referencedColumnName="id"),
       	inverseJoinColumns= @JoinColumn(name="task_id", referencedColumnName="id") )
   @LazyCollection(LazyCollectionOption.EXTRA)
   private Set<EmployeeTask> tasks = new HashSet<EmployeeTask>();

}

И класс EmployeeTask:

@Entity
@Table(name=”task”)
class EmployeeTask {
   @Column(name=”id”)
   public Integer id;

   @ManyToMany(cascade = CascadeType.ALL)
   @JoinTable(name="employee_task",
       	joinColumns=  @JoinColumn(name="task_id", referencedColumnName="id"),
       	inverseJoinColumns= @JoinColumn(name=" employee_id", referencedColumnName="id") )
   @LazyCollection(LazyCollectionOption.EXTRA)
   private Set<Employee> employees = new HashSet<Employee>();

}

А чтобы добавить задачу директору, нужно написать примерно такой код:

Employee director = session.find(Employee.class, 4);
EmployeeTask task = session.find(EmployeeTask.class, 101);
task.employees.add(director);

session.update(task);
session.flush();

Так вот, если поле employees в классе Task имеет аннотацию LazyCollectionOption.EXTRA, то коллекции employees (у класса Task) и коллекции task (у класса Employee) вообще не будут загружены из базы ни разу.

При выполнении этого кода будет вставлена только одна запись в служебную таблицу employee_task, которая, как ты помнишь, выглядит примерно так:

Таблица employee_task:
employee_id task_id
1 1
2 2
5 3
5 4
5 5
4 7
6 8
4 101

Зеленым цветом выделена добавленная строка. Чтобы добавить эту строку, не нужно загружать коллекции из базы, — Hibernate обойдется без этого. Это именно тот случай, когда LazyCollectionOption.EXTRA очень сильно ускоряет работу с базой.

N+1 Problem

Но, конечно, есть у этого режима и обратная сторона. Так, аннотация LazyCollectionOption.EXTRA порождает проблему N+1 Problem.

Если ты решишь пройтись по все комментариям своего пользователя:

User user = session.load(User.class, 1);
List<Comment> comments = user.getComments();
for (Comment comment : comments) {
    System.out.println(comment);
}

То Hibernate будет выполнять по отдельному запросу для каждого объекта Comment. А также еще один дополнительный запрос, чтобы получить количество всех комментариев. Это может существенно замедлить работу кода.

Если у твоего пользователя 1000 комментариев, то для выполнения этого кода Hibernate сделает 1001 запрос в базу, хотя мог бы обойтись и одним. Если бы заранее знал, что тебе понадобятся все объекты данного класса.

1
Задача
Модуль 4. Работа с БД,  14 уровень2 лекция
Недоступна
LazyCollectionOption.EXTRA
LazyCollectionOption.EXTRA
Комментарии (14)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Антон
Уровень 115
19 февраля 2025, 05:03
Основная идея вроде верная, но объяснение путаное. В связях @OneToMany и @ManyToMany по умолчанию используется FetchType.LAZY, а значит, коллекции загружаются только при первом обращении. Однако к этому относятся не только обычные вызовы типа getCollection(), но и такие методы, как size(), add(), remove(). И в этом, собственно, и состоит проблема. Зачем нам загружать всю коллекцию, когда мы просто добавляем новый элемент add(), удаляем старый remove(), или просто узнаем размер коллекции size()? Эту проблему как раз и решает LazyCollectionOption.EXTRA. При ее использовании Hibernate изменяет поведение size(), add(), remove(): • size() выполняет обычный SELECT COUNT(*) без загрузки всей коллекции. • add() сразу выполняет INSERT, без SELECT *. • remove() удаляет запись через DELETE, без загрузки коллекции. В противном случае, если LazyCollectionOption.EXTRA не указана, то при простом добавлении add() у нас будет загружаться вся коллекция.
Дмитрий
Уровень 115
Expert
25 марта, 20:45
атдуши
Евгений
Уровень 97
7 февраля 2025, 07:36
task.employees.add(director);
Каким образом это должно работать???? Никого это не смутило?
Антон
Уровень 115
19 февраля 2025, 04:05
А что не так? Имеешь в виду, что поле employees объявлено как private и к нему нельзя напрямую обращаться?
Михаил Шапошников
Уровень 111
Expert
18 апреля 2024, 13:55
На сколько я понимаю , описание тут проблемы N+1 не верно. Когда у нас есть один пользователь (User) с 1000 комментариями (Comment), и если связь между User и Comment настроена как ленивая загрузка (FetchType.LAZY), то при первом обращении к списку комментариев будет выполнен один дополнительный запрос для их получения. Итого получается два запроса: один для получения пользователя и один для получения всех его комментариев. Однако, если у нас есть запрос, который извлекает 10 пользователей, и для каждого пользователя необходимо загрузить его комментарии, то без соответствующей оптимизации (например, использования JOIN FETCH в JPQL или EntityGraph в JPA) Hibernate выполнит 1 запрос для получения всех пользователей и 10 отдельных запросов для получения комментариев каждого пользователя, в сумме 11 запросов.
fedyaka
Уровень 36
15 мая 2024, 14:36
В любом случае получается одно и то же N+1
Ольга Николенко
Уровень 115
Expert
7 июня 2024, 00:53
возможно что-то в лекции изменилось, но вы говорите о режиме загрузки FetchType.LAZY по параметру Fetch, при котором происходит ленивая, но полная загрузка коллекций а в проблеме N+1 описана аннотация LazyCollection с значением LazyCollectionOption.EXTRA, при которой не происходит полной загрузки коллекции, загрузка каждого элемента коллекции происходит уже в цикле
Max Dudin
Уровень 6
Expert
13 октября 2023, 18:51
а вот он и @OrderColumn всплыл, из задачи решенной пару тройку лекций назад
fedyaka
Уровень 36
8 января 2023, 20:03
есть вопрос а в спринг дата при(где используется hibernate), то при lazy загрузке списка, если мы просто добавляем эллемент в список и сохраняем, то будет ли вытащен весь список а после уже добавлен эллемент или просто сохраниться?
Дмитрий
Уровень 37
17 марта 2024, 20:41
исходя из лекции, если не использовать LazyCollectionOption.EXTRA, то будет дернут весь список. По правьте, если ошибаюсь
Владимир
Уровень 109
Expert
7 января 2023, 13:49
лично меня так сбивает с толку то что класс EmployeeTask ссылается на таблицу task, а ведь есть же еще служебная таблица employee_task, которая связывает сущности Employee и EmployeeTask.
fedyaka
Уровень 36
8 января 2023, 20:01
а ещё там каскадное удаление записей по обе стороны, поэтому если удалят хоть одну запись, то по связям между пользователями и тасками, удалиться вся база данных нахрен(но это не точно, может останется парочка) :))
Anonymous #2957882
Уровень 1
16 января 2023, 17:31
Это упрощенная версия реальных ситуаций, особенно с точки зрения аналитика) Обычно есть основная таблица с некими заявками, 2-3 таблицы с атрибутами, и к каждой справочник, потому что атрибуты это бывает 1-2 буквы или цифры. Когда-то видел, что к 1 таблице порядка 40 таблиц атрибутов и справочников, я даже не понял как с этим разбираться)
Павел
Уровень 111
Expert
10 декабря 2023, 14:17
Да, в приведённом примере класс EmployeeTask следовало бы назвать Task, иначе запутано.