Lazy Loading

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

1. Передісторія проблеми

Коли ти почнеш працювати з реальними базами даних, одразу згадуєш фразу “Передчасна оптимізація — корінь усіх лих”. Тільки ось згадуєш ти її у негативному ключі. Під час роботи з базою даних без оптимізації не обійтися. І працювати з нею потрібно вже на етапі проєктування.

З Hibernate робота з базою даних стає дуже зручною. Ти легко можеш отримати будь-які дочірні об'єкти, якщо правильно розставити анотації @OneToMany та @ManyToMany. Приклад:


@Entity @Table(name="user")
class User {
   @Column(name="id")
   public Integer id;
 
   @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name="user_id")
   public List<Comment> comments;
}

І як просто отримати коментарі користувача:


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

І на тебе тут чекатиме великий сюрприз. Користувач має кілька тисяч коментарів. Якщо ти напишеш такий код, Hibernate, звісно, завантажить всі коментарі користувача. Але це буде дуже повільно: коментарі займуть багато пам'яті тощо.

Тому так писати не можна! Теоретично — можна, а на практиці — ні.

2. Ускладнюємо ситуацію з колекціями

Проблема ще цікавіша. Адже зазвичай тобі ніколи не потрібні усі коментарі користувача. Навіть якщо ти їх відображаєш десь на клієнті, волієш робити це частинами — сторінками.

Тому тобі потрібні такі методи:


public class CommentsManager {
    private static final PAGE_SIZE = 50;
 
    public List<Comment> getCommentsPage(int userId, int pageIndex){
     User user = session.get(User.class, userId);
     List<Comment> comments = user.getComments();
     return comments.subList(pageIndex * PAGE_SIZE , PAGE_SIZE);
    }
 
   public int getCommentsPageCount(int userId) {
     User user = session.get(User.class, userId);
     List<Comment> comments = user.getComments();
     return Math.ceil( comments.size()/PAGE_SIZE);
   }
 
}

Перший метод повертає лише одну сторінку коментарів — 50 штук. Другий спосіб повертає кількість сторінок коментарів. І це найжахливіше. Щоб просто дізнатися кількість коментарів, тобі довелося завантажити з бази всі коментарі!

3. Світло в кінці тунелю

Тож нашими чудовими дочірніми колекціями ніхто не користується. Ні, їх звісно використовують, але лише як частину HQL-запитів. Наприклад:


public class CommentsManager {
      private static final PAGE_SIZE = 50;
 
       public List<Comment> getCommentsPage(int userId, int pageIndex) {
           String hql = "select comments from User where id = :id";
           Query<Comment> query = session.createQuery( hql, Comment.class);
           query.setParameter("id", userId);
           query.setOffset(pageIndex * PAGE_SIZE) ;
           query.setLimit(PAGE_SIZE);
           return query.list();
      }
 
      public int getCommentsPageCount(int userId) {
           String hql = "select count(comments) from User where id = :id";
           Query<Integer> query = session.createQuery( hql, Integer.class);
           query.setParameter("id", userId);
           return Math.ceil(query.singleResult()/PAGE_SIZE );
     }
 
}

Добра новина: ми можемо реалізувати наші методи так, щоб не потрібно було завантажувати зайві дані з бази. Погана новина: так просто працювати з нашими колекціями не можна.

Коментарі (1)
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ
Олександр Рівень 91
9 грудня 2025
У сучасних версіях Hibernate (4.x, 5.x, 6.x) методи setOffset() та setLimit() більше не підтримуються. Замість них слід використовувати: setFirstResult(int firstResult) - замість setOffset() setMaxResults(int maxResults) - замість setLimit() В методі:

public List<Comment> getCommentsPage(int userId, int pageIndex){
     User user = session.get(User.class, userId);
     List<Comment> comments = user.getComments();
     return comments.subList(pageIndex * PAGE_SIZE , PAGE_SIZE);
    }
помилка в логіці return comments.subList(pageIndex * PAGE_SIZE , PAGE_SIZE); другий аргумент теж повинен збільшуватися.

List<E> subList(int fromIndex, int toIndex)
повинно бути щось типу:

public List<Comment> getCommentsPage(int userId, int pageIndex){
     User user = session.get(User.class, userId);
     List<Comment> comments = user.getComments();
     int fromIndex = pageIndex * PAGE_SIZE;
return comments.subList(fromIndex, fromIndex+PAGE_SIZE);
}