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

Поэтому так писать нельзя! В теории – можно, а на практике – нет.

1.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 штук. Второй метод возвращает количество страниц комментариев. И это самое ужасное. Для того, чтобы просто узнать количество комментариев тебе пришлось загрузить из базы все комментарии!

1.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.setParametr("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.setParametr("id", userId);
           	return Math.ceil(query.singleResult()/PAGE_SIZE);
     }
 
}

Хорошая новость – мы можем реализовать наши методы так, чтобы не нужно было загружать лишние данные из базы. Плохая новость – так просто работать с нашими коллекциями нельзя.