JavaRush /Курси /All lectures for UA purposes /LazyCollectionOption.EXTRA

LazyCollectionOption.EXTRA

All lectures for UA purposes
Рівень 1 , Лекція 599
Відкрита

1. Знайомимося з LazyCollectionOption.EXTRA

Але найбільший інтерес являє собою значення LazyCollectionOption.EXTRA. Якщо ти вказуєш його як значення анотації @LazyCollection, то Hibernate відтягуватиме завантаження елементів колекції якомога довше.

Якщо ти спробуєш отримати кількість елементів колекції:


User user = session.load(User.class, 1);
List<Comment> comments = user.getComments();
int count = comments.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 = comments.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;
}

2. Головна перевага 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 дуже прискорює роботу з базою.

3. 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)
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ
Omar Cooley Рівень 1
23 липня 2025
Wow, that really highlights how important it is to understand what's happening under the hood with Hibernate. I’ve definitely run into similar issues where lazy loading led to way more queries than expected. Using proper fetching strategies like `JOIN FETCH` or tweaking the fetch type can make a huge difference in performance. Thanks for explaining it so clearly — it's a good reminder to always profile and review query behavior, especially when dealing with large data sets. ragdoll hit