如何对 JPA 查询进行分页

Posted

技术标签:

【中文标题】如何对 JPA 查询进行分页【英文标题】:How to paginate a JPA Query 【发布时间】:2013-04-18 16:56:40 【问题描述】:

我有一个提交表,其中包含 IDNameCode 等列以及其他属性。我的要求是根据提到的属性搜索记录并返回一个分页集。

这是我正在寻找的伪代码:

searchSubmission(searchFilter sf,pageIndex,noOfRecords) 
   query = 'from submisssion where code=sf.code or id=sf.id order by id start_from (pageIndex*noOfRecords) limit noOfRecords'
   return result();

似乎有很多选项,例如CriteriaBuilderNamedQuery 等。在这种情况下,哪个选项最有效?

【问题讨论】:

【参考方案1】:

对于所有 JPA 查询对象(本地 SQL 查询除外),您将通过 setMaxResults(int) 和 setFirstResult(int) 方法使用分页。例如:

  return em.createNamedQuery("yourqueryname", YourEntity.class)
      .setMaxResults(noOfRecords)
      .setFirstResult(pageIndex * noOfRecords)
      .getResultList();

JPA 将为您执行分页。

命名查询只是预定义的,可以缓存,而其他类型则是动态创建的。 所以选择使用 JPQL,如:

Query query = em.createQuery("SELECT s FROM Submission s WHERE s.code = :code or s.id = :id ORDER BY s.id", Submission.class);

或 CriteriaBuilder api 形成类似的查询:

    CriteriaBuilder qb = em.getCriteriaBuilder();
    CriteriaQuery<Submission> cq = qb.createQuery(Submission.class);

    Root<Submission> root = cq.from(Submission.class);
    cq.where( qb.or( 
        qb.equal(root.get("code"), qb.parameter(String.class, "code")),
        qb.equal(root.get("id"), qb.parameter(Integer.class, "id"))
    ));
    Query query = em.createQuery(cq);

不要忘记使用 query.setParameter("id", sf.id) 设置参数值。

【讨论】:

【参考方案2】:

你可以在Spring的repository方法中使用Pageable

@Repository
public interface StateRepository extends JpaRepository<State, Serializable> 

@Query("select state from State state where state.stateId.stateCode = ?1")
public State findStateByCode(String code, Pageable pageable);

而在服务层,可以创建Pageable对象:

@Autowire
StateRepository stateRepository;

public State findStateServiceByCode(String code, int page, int size) 
    Pageable pageable = new PageRequest(page, size);
    Page<Order> statePage = stateRepository.findStateByCode(code, pageable);
    return statePage.getContent();

【讨论】:

构造函数 new PageRequest(page, size) 已弃用。使用 Pageable pageable = PageRequest.of(page, size) 代替 如果我没记错的话,这是特定于 Spring 的。你能在你的答案中指定它吗?【参考方案3】:

您可以将 JPA 分页用于实体查询和原生 SQL。

为了限制底层查询ResultSet 的大小,JPA Query 接口提供了setMaxResults 方法。

浏览下一页需要将结果集定位到最后一页结束的位置。为此,JPA Query 接口提供了setFirstResult 方法。

在 JPQL 查询中使用分页如下所示:

List<Post> posts = entityManager.createQuery("""
    select p
    from Post p
    order by p.createdOn
    """)
    .setFirstResult(10)
    .setMaxResults(10)
    .getResultList();

Criteria API 是一样的,因为您需要从CriteriaQuery 创建一个Query

CriteriaBuilder qb = em.getCriteriaBuilder();
CriteriaQuery<Post> cq = qb.createQuery(Post.class);

Root<Post> root = cq.from(Post.class);
cq.orderBy(qb.asc(root.get("createdOn")));

List<Post> posts = em.createQuery(cq)
    .setFirstResult(10)
    .setMaxResults(10)
    .getResultList();

【讨论】:

虽然 setMaxResults 和 setFirstResult api 确实存在并且可用于 Native SQL 查询,但我会犹豫使用它们。它们的工作方式取决于提供者,但由于它们只是通过 Native SQL 语句,因此唯一可以进行的过滤是对完整返回的结果进行过滤。同样,依赖于实现,但是 setFirstResult/setMaxResults 允许提供者在 JPQL 或条件查询时将 DB 特定的分页(如 rownum 过滤)注入到生成的 SQL 中。您最好对本机 SQL 查询执行相同操作以避免网络/数据库命中。 @Chris HIbernate 只是在将 SQL 发送到数据库之前对其进行修改。没有额外的网络/数据库命中。 它是否修改了它发送到数据库的本机 SQL 字符串?对于 JPQL/CriteriaQuery/HB 查询,当然,它可以修改它生成的 SQL,但我很惊讶它对于 Native SQL 查询会这样做,因为担心它会弄乱 SQL。如前所述,这是特定于 JPA 提供程序的 - 其他人不会触及您的 Native SQL 查询,因此依赖于其他方式进行分页,这比您在 SQL 本身中可以和应该做的要少。 您可能想知道它是如何工作的,但作为一名*** Hibernate 提交者,我可以为您确认这一点。这是GitHub 的一个示例。至于 JPA 的可移植性,那是神话。人们并没有真正改变 JPA 提供者,而且绝大多数人都使用 Hibernate,所以这不是现实生活中的问题。 感谢您的信息,但令我惊讶的是他们这样做,而不是他们如何做到的。我会避免进入 JPA 供应商辩论,因为这个问题/线程不在 Hibernate 或您对 JPA 的看法上,我怀疑有人关心我自己的。

以上是关于如何对 JPA 查询进行分页的主要内容,如果未能解决你的问题,请参考以下文章

JPA分页查询与条件分页查询

如何通过分页在 Spring Data JPA 中的 SELECT 子句中按别名对投影进行排序?

使用Spring Data JPA进行数据分页与排序

Spring Data Jpa: 分页和排序

Spring Data Jpa: 分页和排序

在Spring Boot中使用Spring-data-jpa实现分页查询(转)