Перейти к основному содержимому

Пагинация JPA

· 3 мин. чтения

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

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

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 достаточно просто получить общее количество объектов :

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, поэтому его легко импортировать и запускать как есть.