Знакомимся с 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 запрос в базу, хотя мог бы обойтись и одним. Если бы заранее знал, что тебе понадобятся все объекты данного класса.

undefined
1
Задача
Модуль 4. Работа с БД, 14 уровень, 2 лекция
Недоступна
task1403
Есть два класса-энтити: Employee и StatisticView. У Employee замени параметр fetch аннотации...