使用JPA和Hibernate查询分页
创始人
2024-03-17 18:48:04
0

介绍

受到我最近给出的StackOverflow答案的启发,我决定是时候写一篇关于使用JPA和Hibernate时查询分页的文章了。

在本文中,您将了解如何使用查询分页来限制 JDBC大小并避免获取不必要的数据。ResultSet

如何在#Hibernate中使用查询分页来限制 JDBC 结果集的大小并避免获取不必要的数据。@vlad_mihalceahttps://t.co/fkd8ne1mYjpic.twitter.com/Ca78OhlIP1

— Java (@java)2018 年 10 月 12 日

域模型

现在,假设我们在应用程序中定义了以下实体类:PostPostComment

类是父实体,而子实体是子实体,因为它与实体有关联。两个实体都实现了提供用于访问基础实体标识符的协定的接口。PostPostComment@ManyToOnePostIdentifiable

接下来,我们将在数据库中保存以下内容和实体:PostPostComment

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 LocalDateTime timestamp = LocalDateTime.of(     201810912000 );   int commentsSize = 5;   LongStream.range(150).forEach(postId -> {     Post post = new Post();     post.setId(postId);     post.setTitle(         String.format("Post nr. %d", postId)     );     post.setCreatedOn(          Timestamp.valueOf(             timestamp.plusMinutes(postId)         )     );       LongStream.range(1, commentsSize + 1).forEach(commentOffset -> {         long commentId = ((postId - 1) * commentsSize) + commentOffset;           PostComment comment = new PostComment();                comment.setId(commentId);         comment.setReview(             String.format("Comment nr. %d", comment.getId())         );         comment.setCreatedOn(             Timestamp.valueOf(                 timestamp.plusMinutes(commentId)             )         );           post.addComment(comment);       });           entityManager.persist(post); });

限制结果集大小

为了限制基础查询大小,JPA接口提供了setMaxResults方法。ResultSetQuery

因此,在执行以下 JPQL 查询时:

1 2 3 4 5 6 7 8 9 10 11 List posts = entityManager .createQuery(     "select p " +     "from Post p " +     "order by p.createdOn ") .setMaxResults(10) .getResultList();   assertEquals(10, posts.size()); assertEquals("Post nr. 1", posts.get(0).getTitle()); assertEquals("Post nr. 10", posts.get(9).getTitle());

Hibernate在PostgreSQL上生成以下SQL语句:

1 2 3 4 5 6 SELECT p.id AS id1_0_,        p.created_on AS created_2_0_,        p.title AS title3_0_ FROM post p ORDER BY p.created_on LIMIT 10

在 SQL Server 2012(或更高版本)上,Hibernate 将执行以下 SQL 查询:

1 2 3 4 5 6 SELECT p.id AS id1_0_,        p.created_on AS created_2_0_,        p.title AS title3_0_ FROM post p ORDER BY p.created_on OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY

因此,SQL 分页查询适用于基础数据库引擎功能。

使用查询分页时,使用 of 是强制性的,因为 SQL 不保证任何特定的顺序,除非我们通过子句提供一个。ORDER BYORDER BY

使用偏移来定位结果集

如果上一个查询是给定分页查询的第一页的典型查询,则导航下一页需要将结果集定位在最后一页结束的位置。为此,JPA接口提供了setFirstResult方法。Query

1 2 3 4 5 6 7 8 9 10 11 12 List posts = entityManager .createQuery(     "select p " +     "from Post p " +     "order by p.createdOn ") .setFirstResult(10) .setMaxResults(10) .getResultList();   assertEquals(10, posts.size()); assertEquals("Post nr. 11", posts.get(0).getTitle()); assertEquals("Post nr. 20", posts.get(9).getTitle());

在 PostgreSQL 上运行之前的 JPQL 查询时,Hibernate 执行以下 SQL SELECT 语句:

1 2 3 4 5 6 7 SELECT p.id AS id1_0_,        p.created_on AS created_2_0_,        p.title AS title3_0_ FROM post p ORDER BY p.created_on LIMIT 10 OFFSET 10

在 SQL Server 2012(或更高版本)上,Hibernate 将生成以下 SQL 查询:

1 2 3 4 5 6 SELECT p.id AS id1_0_,        p.created_on AS created_2_0_,        p.title AS title3_0_ FROM post p ORDER BY p.created_on OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY

DTO投影查询

JPA 查询分页不限于仅返回实体的实体查询。您也可以将其用于DTO投影。

假设我们有以下DTO:PostCommentSummary

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class PostCommentSummary {       private Number id;     private String title;     private String review;       public PostCommentSummary(             Number id,             String title,             String review) {         this.id = id;         this.title = title;         this.review = review;     }       public PostCommentSummary() {}       //Getters omitted for brevity }

运行以下 DTO 投影查询时:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 List summaries = entityManager .createQuery(     "select new " +     "   com.vladmihalcea.book.hpjp.hibernate.fetching.PostCommentSummary( " +     "       p.id, p.title, c.review " +     "   ) " +     "from PostComment c " +     "join c.post p " +     "order by c.createdOn") .setMaxResults(10) .getResultList();   assertEquals(10, summaries.size()); assertEquals("Post nr. 1", summaries.get(0).getTitle()); assertEquals("Comment nr. 1", summaries.get(0).getReview());   assertEquals("Post nr. 2", summaries.get(9).getTitle()); assertEquals("Comment nr. 10", summaries.get(9).getReview());

Hibernate将分页子句附加到底层SQL查询:

1 2 3 4 5 6 7 SELECT p.id AS col_0_0_,        p.title AS col_1_0_,        c.review AS col_2_0_ FROM post_comment c INNER JOIN post p ON c.post_id=p.id ORDER BY c.created_on LIMIT 10

有关使用 JPA 和 Hibernate 进行 DTO 投影的更多详细信息,请查看本文。

本机 SQL 查询

JPA 查询分页不限于实体查询,例如 JPQL 或条件 API。您也可以将其用于本机 SQL 查询。

1 2 3 4 5 6 7 8 9 10 11 12 List posts = entityManager .createNativeQuery(     "select p.id as id, p.title as title " +     "from post p " +     "order by p.created_on", Tuple.class) .setFirstResult(10) .setMaxResults(10) .getResultList();   assertEquals(10, posts.size()); assertEquals("Post nr. 11", posts.get(0).get("title")); assertEquals("Post nr. 20", posts.get(9).get("title"));

运行上述 SQL 查询时,Hibernate 会附加特定于 DB 的分页子句:

1 2 3 4 5 6 SELECT p.id AS id,        p.title AS title FROM post p ORDER BY p.created_on LIMIT 10 OFFSET 10

加入抓取和分页

但是,如果我们尝试在实体查询中使用子句,同时也使用 JPA 分页:JOIN FETCH

1 2 3 4 5 6 7 8 9 List posts = entityManager.createQuery(     "select p " +     "from Post p " +     "left join fetch p.comments " +     "order by p.createdOn", Post.class) .setMaxResults(10) .getResultList();   assertEquals(10, posts.size());

休眠将发出以下警告消息:

1 HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!

并且执行的 SQL 查询将缺少分页子句:

1 2 3 4 5 6 7 8 9 10 11 12 SELECT p.id AS id1_0_0_,        c.id AS id1_1_1_,        p.created_on AS created_2_0_0_,        p.title AS title3_0_0_,        c.created_on AS created_2_1_1_,        c.post_id AS post_id4_1_1_,        c.review AS review3_1_1_,        c.post_id AS post_id4_1_0__,        c.id AS id1_1_0__ FROM post p LEFT OUTER JOIN post_comment c ON p.id=c.post_id ORDER BY p.created_on

这是因为 Hibernate 希望完全获取实体及其集合,如子句所示,而 SQL 级分页可能会截断可能使父实体在集合中具有较少元素。JOIN FETCHResultSetPostcomments

警告的问题在于Hibernate将获取andentities的乘积,并且由于结果集的大小,查询响应时间将很长。HHH000104PostPostComment

为了解决此限制,您必须使用窗口函数查询:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 List posts = entityManager .createNativeQuery(     "select * " +     "from (   " +     "    select *, dense_rank() OVER (ORDER BY post_id) rank " +     "    from (   " +     "        select p.*, pc.* " +     "        from post p  " +     "        left join post_comment pc on p.id = pc.post_id  " +     "        order by p.created_on " +     "    ) p_pc " +     ") p_pc_r " +     "where p_pc_r.rank <= :rank", Post.class) .setParameter("rank"10) .unwrap(NativeQuery.class) .addEntity("p", Post.class) .addEntity("pc", PostComment.class) .setResultTransformer(DistinctPostResultTransformer.INSTANCE) .getResultList();

有关使用窗口函数修复问题及其代码的更多详细信息,请查看本文。HHH000104DistinctPostResultTransformer

为什么不改用查询流式处理?

JPA 2.2 添加了该方法,您可能认为它是分页的有效替代方案。但是,流结果不会向查询计划程序提供结果集大小,因此可能会选择次优的执行计划。因此,在获取少量数据时,使用分页比流式传输更有效。getResultStreamQuery

有关为什么分页比流式传输更有效的更多详细信息,请查看本文。

键集分页

Markus Winand撰写了《SQL Performance Explained》一书,他提倡使用Keyset分页而不是Offset。尽管偏移分页是 SQL 标准功能,但有两个原因让您更喜欢密钥集分页:

  • 性能(索引必须扫描到偏移量,而对于键集分页,我们可以直接转到按谓词和过滤条件匹配顺序的第一个索引条目)
  • 正确性(如果在两者之间添加元素,偏移分页将无法提供一致的读取)

即使 Hibernate 不支持键集分页,您也可以使用本机 SQL 查询来实现此目的。

结论

获取所需数量的数据是数据访问性能方面最重要的技巧之一。提取数据时,分页允许您控制结果集大小,以便即使基础数据集随时间增长,性能也保持稳定。

虽然键集分页为大型结果集提供了更好的性能,但如果您可以使用正确的过滤谓词缩小扫描的数据集,则偏移分页的性能将相当好。为了获得一致的读取,您必须确保扫描的数据集始终以这样一种方式进行排序,即新条目附加到集的末尾,而不是混合在旧条目之间。

相关内容

热门资讯

原创 中... 12月25日,家纺企业富安娜披露了关于中信证券固定收益类理财产品逾期兑付的进展公告。公告显示,公司近...
封关临近!海南自贸港政策红利释... 交易所数据显示,2025年12月26日09时47分,京粮控股当前价格为8.92元,涨幅为9.99%,...
字节跳动通报:120名员工被辞... 12月25日,字节跳动披露2025年三季度内部违规案例的处理情况。通报显示,三季度共有120名员工因...
上亿理财难收回,家纺龙头富安娜... 12月25日晚,家纺龙头企业深圳市富安娜家居用品股份有限公司(以下简称富安娜,002327.SZ)发...
华院计算取得法律要素图谱辅助类... 国家知识产权局信息显示,华院计算技术(上海)股份有限公司取得一项名为“一种法律要素图谱辅助类案推荐方...
严重违背人伦底线,犯罪手段特别... 据“遵义审判”消息,2025年12月26日,贵州省遵义市中级人民法院依法对被告人刘仲杰故意杀人案进行...
原创 死... 死刑判决能否抚平受害者家属的创伤?法律与心理的双重拷问 当一纸死刑判决书尘埃落定,法庭外的受害者家属...
贵州工会“一函两书”典型案例⑩... 编者按:“一函两书”制度,是工会组织开展劳动法律监督,联合检察院、法院、人社等部门提醒用人单位落实好...
建元信托4.98亿诉讼纠纷再起... 今年以来,建元信托遭遇11起诉讼案件,包括2起大额诉讼。 文/每日财报 楚风 建元信托的诉讼风险仍...
【学政策·人事人才篇】取得哪些... 来源:人力资源和社会保障部微信公众号