Lazy Loading

Модуль 4. Работа с БД
14 уровень , 0 лекция
Открыта

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

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

Комментарии (7)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Андрей Уровень 109
5 июля 2024
Мне кажется, перед получением порции комментариев они должны быть предварительно отсортированы. Иначе может произвольный набор каждый раз возвращаться
NodeOne Уровень 41 Expert
3 мая 2024
у JPARepository есть метод count(). Специально смотреть не полезу но когда вся вот эта возня со страницами идет, его использую.
Дмитрий Уровень 117 Expert
24 января 2024
У поля PAGE_SIZE отсутствует тип. Поиск опечаток даже азарт вызывает. Это как дополнительное упражнение.
Надежда Уровень 104 Expert
4 октября 2023
Исправьте название метода: вместо query.setParametr надо query.setParameter
JavaCoder Уровень 51
10 апреля 2023
Вместо query.setOffset и query.setLimit видимо нужно использовать query.setFirstResult и query.setMaxResults.
Надежда Уровень 104 Expert
4 октября 2023
Различия связаны с тем, какой фреймворк или библиотека используется в проекте. Если вы работаете с JPA, то используйте setFirstResult() и setMaxResults(). Если вы работаете с Hibernate или напрямую с SQL, то setOffset() и setLimit() могут быть предпочтительными.
JavaCoder Уровень 51
6 октября 2023
Так я и работаю с Hibernate (6.1.1.Final), но методов setOffset() и setLimit() у Query не нашел.