Знакомимся с 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 запрос в базу, хотя мог бы обойтись и одним. Если бы заранее знал, что тебе понадобятся все объекты данного класса.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ