本文说明了如何在Java 持久性 API 中实现分页。
它解释了如何使用基本 JQL 和更安全、基于标准的 API 进行分页,讨论了每个实现的优点和已知问题。
实现分页的最简单方法是使用Java 查询语言 – 创建一个查询并通过setMaxResults和s etFirstResult 对其进行配置:
- 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();
Copy
API 很简单:
对于更完整的分页解决方案,我们还需要获取总结果计数:
- Query queryTotal = entityManager.createQuery
- ("Select count(f.id) from Foo f");
- long countResult = (long)queryTotal.getSingleResult();
Copy
计算最后一页也非常有用:
- int pageSize = 10;
- int pageNumber = (int) ((countResult / pageSize) + 1);
Copy
请注意,这种获取结果集总数的方法确实需要额外的查询(针对计数)。
一个简单的替代分页策略是首先检索完整的 ID,然后根据这些 ID 检索完整的实体。这样可以更好地控制实体获取 - 但这也意味着它需要加载整个表才能检索 id:
- 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();
Copy
最后,还要注意,它需要 2 个不同的查询来检索完整结果。
接下来,让我们看看如何利用 JPA 标准 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();
Copy
当目标是创建动态、故障安全查询时,这很有用。与“硬编码”、“基于字符串”的 JQL 或 HQL 查询相比,JPA 条件减少了运行时失败,因为编译器会动态检查查询错误。
使用 JPA 标准以非常简单的方式获取实体总数:
- CriteriaQuery<Long> countQuery = criteriaBuilder
- .createQuery(Long.class);
- countQuery.select(criteriaBuilder.count(
- countQuery.from(Foo.class)));
- Long count = entityManager.createQuery(countQuery)
- .getSingleResult();
Copy
最终结果是使用 JPA 标准 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;
- }
Copy
本文探讨了 JPA 中提供的基本分页选项。
有些缺点 - 主要与查询性能有关,但这些缺点通常被改进的控制和整体灵活性所抵消。
这个Spring JPA教程的实现可以在GitHub项目中找到 - 这是一个基于Maven的项目,所以它应该很容易导入和运行。