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 );
     }
 
}

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