Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Постраничный вывод

Блог с тысячами статей не может показать их все на одной странице. Пагинация (постраничный вывод) загружает по порции: статьи 1–10, потом 11–20 и так далее. В Hibernate это два метода запроса; нужные строки возвращает сама база, поэтому память не тратится впустую.

Страница статей

Пример создаёт 25 статей и показывает третью страницу по десять статей:

// src/main/java/com/example/blog/Main.java
package com.example.blog;
 
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
import java.util.List;
 
public class Main {
    static final int PAGE_SIZE = 10;
 
    public static void main(String[] args) {
        try (EntityManagerFactory emf = Persistence.createEntityManagerFactory("blog")) {
            emf.runInTransaction(em -> {
                Author anna = new Author("Анна");
                em.persist(anna);
                for (int i = 1; i <= 25; i++) {
                    em.persist(new Post("Статья " + i, anna));
                }
            });
 
            emf.runInTransaction(em -> {
                long total = em.createQuery("select count(p) from Post p", Long.class).getSingleResult();
                long pages = (total + PAGE_SIZE - 1) / PAGE_SIZE;
                IO.println("Статей: " + total + ", страниц: " + pages);
 
                int page = 3;
                List<Post> posts = em.createQuery("select p from Post p order by p.id", Post.class)
                        .setFirstResult((page - 1) * PAGE_SIZE)
                        .setMaxResults(PAGE_SIZE)
                        .getResultList();
                IO.println("Страница " + page + ": " + posts.stream().map(Post::getTitle).toList());
            });
        }
    }
}
Hibernate: select count(p1_0.id) from Post p1_0
Статей: 25, страниц: 3
Hibernate: select p1_0.id,p1_0.author_id,p1_0.title,p1_0.views from Post p1_0 order by p1_0.id offset ? rows fetch first ? rows only
Страница 3: [Статья 21, Статья 22, Статья 23, Статья 24, Статья 25]
МетодЗначение
setFirstResult(n)Пропустить первые n строк; счёт с нуля
setMaxResults(n)Вернуть не больше n строк

Номера страниц — ваше соглашение: для третьей страницы по десять строк нужно пропустить (3 - 1) * 10 = 20 строк. Hibernate переводит два метода в синтаксис вашей базы: offset ... fetch first ... rows only в H2 и PostgreSQL, limit ... offset ... в MySQL. Для общего числа страниц нужен отдельный запрос count.

Пагинация и join fetch

Совмещать страницу с join fetch коллекции непросто: после соединения каждая статья повторяется столько раз, сколько у неё комментариев, и «первые 2 строки» — это не «первые 2 статьи». Hibernate 7 решает это сам, через подзапрос:

// src/main/java/com/example/blog/FetchPage.java
List<Post> posts = em.createQuery("select p from Post p join fetch p.comments order by p.id", Post.class)
        .setFirstResult(0)
        .setMaxResults(2)
        .getResultList();
Hibernate: select ... from (select p1_0.id,p1_0.author_id,p1_0.title,p1_0.views from Post p1_0 where exists(select 1 from Comment c1_0 where p1_0.id=c1_0.post_id) order by p1_0.id offset ? rows fetch first ? rows only) p1_0(id,author_id,title,views) join Comment c1_0 on p1_0.id=c1_0.post_id order by p1_0.id
[Привет, Hibernate, Типы record в Java 25]

Внутренний запрос берёт две статьи, внешний добавляет их комментарии. Старые версии Hibernate вместо этого загружали все строки и вырезали страницу в памяти.

Далёкие страницы

offset заставляет базу прочитать и выбросить все пропущенные строки, поэтому страница номер 5000 работает медленно. Для бесконечных лент используют условие по последнему увиденному ключу: where p.id > :lastId order by p.id вместе с setMaxResults. Это называется пагинацией по ключу (keyset pagination).

Частая ошибка

Запрос страницы без order by возвращает строки в том порядке, который удобен базе. Порядок может меняться от запроса к запросу: одна статья попадает на две страницы, а другая не показывается вовсе. Сообщения об ошибке нет — баг видят только пользователи. Всегда сортируйте, причём по уникальному столбцу: если сортируете по дате, добавьте id вторым ключом.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.