«1. Обзор

В этой статье показано, как реализовать разбиение на страницы в Java Persistence API.

В нем объясняется, как выполнять разбиение по страницам с помощью базового JQL и более безопасных API на основе критериев, обсуждаются преимущества и известные проблемы каждой реализации.

2. Разбиение на страницы с помощью JQL и API setFirstResult(), setMaxResults()

Самый простой способ реализовать разбиение на страницы — использовать язык запросов Java — создать запрос и настроить его с помощью setMaxResults и setFirstResult: ~~ ~

Query query = entityManager.createQuery("From Foo");
int pageNumber = 1;
int pageSize = 10;
query.setFirstResult((pageNumber-1) * pageSize); 
query.setMaxResults(pageSize);
List <Foo> fooList = query.getResultList();

API прост:

    setFirstResult(int): Устанавливает позицию смещения в результирующем наборе для начала нумерации страниц setMaxResults(int): Устанавливает максимальное количество объектов, которые должны быть включены на страницу

2.1 . Общее количество и последняя страница

Для более полного решения разбиения на страницы нам также потребуется получить общее количество результатов:

Query queryTotal = entityManager.createQuery
    ("Select count(f.id) from Foo f");
long countResult = (long)queryTotal.getSingleResult();

Вычисление последней страницы также очень полезно:

int pageSize = 10;
int pageNumber = (int) ((countResult / pageSize) + 1);

Обратите внимание, что этот подход к получению общего количества результирующего набора требует дополнительного запроса (для количества).

3. Разбиение на страницы с помощью JQL с использованием идентификаторов сущностей

Простая альтернативная стратегия разбиения на страницы состоит в том, чтобы сначала получить полные идентификаторы, а затем — на их основе — получить полные объекты. Это позволяет лучше контролировать выборку сущностей, но это также означает, что для получения идентификаторов необходимо загрузить всю таблицу:

Query queryForIds = entityManager.createQuery(
  "Select f.id from Foo f order by f.lastName");
List<Integer> fooIds = queryForIds.getResultList();
Query query = entityManager.createQuery(
  "Select f from Foo e where f.id in :ids");
query.setParameter("ids", fooIds.subList(0,10));
List<Foo> fooList = query.getResultList();

Наконец, также обратите внимание, что для получения полных результатов требуется 2 разных запроса.

4. Разбиение на страницы с помощью JPA Criteria API

Далее давайте посмотрим, как мы можем использовать API JPA Criteria для реализации разбиения на страницы:

int pageSize = 10;
CriteriaBuilder criteriaBuilder = entityManager
  .getCriteriaBuilder();
CriteriaQuery<Foo> criteriaQuery = criteriaBuilder
  .createQuery(Foo.class);
Root<Foo> from = criteriaQuery.from(Foo.class);
CriteriaQuery<Foo> select = criteriaQuery.select(from);
TypedQuery<Foo> typedQuery = entityManager.createQuery(select);
typedQuery.setFirstResult(0);
typedQuery.setMaxResults(pageSize);
List<Foo> fooList = typedQuery.getResultList();

Это полезно, когда целью является создание динамических, отказоустойчивых безопасные запросы. В отличие от «жестко запрограммированных», «строковых» запросов JQL или HQL, JPA Criteria уменьшает количество сбоев во время выполнения, поскольку компилятор динамически проверяет наличие ошибок запроса.

Используя JPA Criteria, достаточно просто получить общее количество объектов:

CriteriaQuery<Long> countQuery = criteriaBuilder
  .createQuery(Long.class);
countQuery.select(criteriaBuilder.count(
  countQuery.from(Foo.class)));
Long count = entityManager.createQuery(countQuery)
  .getSingleResult();

Конечным результатом является полное решение для разбиения на страницы с использованием JPA Criteria API:

int pageNumber = 1;
int pageSize = 10;
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();

CriteriaQuery<Long> countQuery = criteriaBuilder
  .createQuery(Long.class);
countQuery.select(criteriaBuilder
  .count(countQuery.from(Foo.class)));
Long count = entityManager.createQuery(countQuery)
  .getSingleResult();

CriteriaQuery<Foo> criteriaQuery = criteriaBuilder
  .createQuery(Foo.class);
Root<Foo> from = criteriaQuery.from(Foo.class);
CriteriaQuery<Foo> select = criteriaQuery.select(from);

TypedQuery<Foo> typedQuery = entityManager.createQuery(select);
while (pageNumber < count.intValue()) {
    typedQuery.setFirstResult(pageNumber - 1);
    typedQuery.setMaxResults(pageSize);
    System.out.println("Current page: " + typedQuery.getResultList());
    pageNumber += pageSize;
}

5. Заключение

Это В статье были рассмотрены основные параметры разбивки на страницы, доступные в JPA.

Некоторые из них имеют недостатки — в основном связанные с производительностью запросов, но они обычно компенсируются улучшенным контролем и общей гибкостью.

Реализацию этого учебного пособия по Spring JPA можно найти в проекте GitHub — это проект на основе Maven, поэтому его легко импортировать и запускать как есть.