使用JPA和Hibernate查询分页

news2024/9/23 3:20:07

介绍

受到我最近给出的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<Post> 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<Post> 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<PostCommentSummary> 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<Tuple> 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<Post> 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<Post> 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 查询来实现此目的。

结论

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/61147.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

pytorch深度学习实战lesson32

第三十二课 分布式训练 这个是15年的时候沐神在 CMU 装的一个小机群&#xff0c;里面有30台机器&#xff0c;各机群有大概60块 GPU &#xff0c; 60块 GPU一共花了三四万美金的样子&#xff0c;就是大概20万人民币。沐神表示最亏的是当年他们跑了太多深度学习的实验&#xff0c…

C语言-const char*,char const*,char *const理解

By: Ailson Jack Date: 2022.12.04 个人博客&#xff1a;http://www.only2fire.com/ 本文在我博客的地址是&#xff1a;http://www.only2fire.com/archives/150.html&#xff0c;排版更好&#xff0c;便于学习&#xff0c;也可以去我博客逛逛&#xff0c;兴许有你想要的内容呢。…

传奇外网开服教程-GEE传奇外网全套架设教程

版本不同&#xff0c;所用的引擎和配置也会不同&#xff0c;但是架设方法都是大同小异&#xff0c;今天明杰给大家分享GEE引擎的外网架设教程。​ 需要准备的东西&#xff1a;DBC200版本&#xff0c;补丁&#xff0c;客户端&#xff0c;服务器&#xff0c;备案域名&#xff0c…

【Typora】Typora 新手入门参数配置记录

目录 写在前面 更改图片大小 更换高亮背景 更换主题 写在前面 最近发现一款记笔记的软件——Typora&#xff0c;极简清爽的外观一下子就把我给吸引住了&#xff0c;它支持Markdown 的格式记录&#xff0c;可以让笔记更加有条理、美观&#xff0c;至于 typora 的一些写作语法…

Android入门第43天-Activity与Activity间的互相传值

介绍 今天的课程会比较好玩&#xff0c;我们在之前的Service篇章中看到了一种putExtras和getExtras来进行activity与service间的传值。而恰恰这种传值其实也是Android里的通用传值法。它同样可以适用在activity与activity间传值。 Android中的传值 传单个值 传多个值 具体我…

Spring注解(简便地使用 Bean )

目录 0. 前置工作 1. 将 Bean 存储到容器 2. 对象注入&#xff08;对象装配&#xff09;【从容器中将对象读取出来】 0. 前置工作 创建Maven项目后&#xff0c;在pom.xml中添加Spring所必须的依赖。 <dependencies><dependency><groupId>org.springframe…

22个每个程序员都应该知道的 Git 命令

在这篇文章中&#xff0c;我写了一个快速学习 git 命令的备忘单。它将包括开发人员每天使用的命令&#xff0c;如 git add、git commit、git pull、git fetch&#xff0c;并共享其他有用的 git 命令。 我一直使用Git的一些命令&#xff0c;今天这个列表清单&#xff0c;希望也…

LC-6256. 将节点分成尽可能多的组(二分图判定+BFS)【周赛322】

6256. 将节点分成尽可能多的组 难度困难8 给你一个正整数 n &#xff0c;表示一个 无向 图中的节点数目&#xff0c;节点编号从 1 到 n 。 同时给你一个二维整数数组 edges &#xff0c;其中 edges[i] [ai, bi] 表示节点 ai 和 bi 之间有一条 双向 边。注意给定的图可能是不…

第4章 R语言编程基础——数据整理与预处理

目录 4.1 经济/金融数据库 4.1.1 金融数据与数据库 4.1.2 国外金融数据库概况 4.1.3 国内金融数据库概况 4.1.4 数据的主要内容 4.2 数据格式 4.3 数据的导入 4.3.1 从控制台上输入数据 4.3.2 上市公司财务报表信息读取 4.4 [数据的预处理] 4.1.1 时序数据的预处理 4.1.2…

[附源码]Python计算机毕业设计Django汽车租赁管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

WEB前端网页设计 HTML网页代码 基础参数(二)

html文件调用css文件 <link rel"stylesheet" type"text/css" href"css文件相对路径"/> 设置颜色 Style"color:green;" 设置字体大小 font-size:50px; 设置边框 border&#xff1a;groove&#xff1b; 设置长、宽 heigh…

【通信原理】数字基带传输的线路码型

数字基带传输的线路码型 简单介绍数字基带传输的线路码型的信号波形的特点&#xff0c;以及生成方法。注意观察频谱。文末附Matlab代码。 以下包括双极性NRZ、单极型NRZ、双极型RZ、单极型RZ、差分码&#xff0c;曼切斯特码/数字双相码、密勒码、CMI码、AMI码、HDB3码。 参数…

Spring中Bean的生命周期

先直接说出过程&#xff0c;再来演示具体的操作 过程 简化来说就是 1、首先是实例化Bean&#xff0c;当客户向容器请求一个尚未初始化的bean时&#xff0c;或初始化bean的时候需要注入另一个尚末初始化的依赖时&#xff0c;容器就会调用doCreateBean()方法进行实例化&#xf…

Java多线程之:队列同步器AbstractQueuedSynchronizer原理剖析

Java多线程之&#xff1a;队列同步器AbstractQueuedSynchronizer原理剖析 文章目录Java多线程之&#xff1a;队列同步器AbstractQueuedSynchronizer原理剖析一、AQS的核心思想二、AQS中关键的内部结构一、Node内部类二、CLH队列三、同步状态 state四、Condition条件队列三、AQS…

houdini 之copy to points

将第一个输入中的几何图形复制到第二个输入的点上。 属性备注Source Group几何体来源Target Points要复制到的目标点集合Show Guide Geometry是否显示该操作预览流程Pack and Instance在复制之前将输入几何体打包到嵌入式打包图元中。这导致输入几何被每个副本共享&#xff08;…

跟着实例学Go语言(一)

本教程全面涵盖了Go语言基础的各个方面。一共80个例子&#xff0c;每个例子对应一个语言特性点&#xff0c;非常适合新人快速上手。 教程代码示例来自go by example&#xff0c;文字部分来自本人自己的理解。 本文是教程系列的第一部分&#xff0c;共计20个例子、约1万字。 目…

电子学会2021年3月青少年软件编程(图形化)等级考试试卷(四级)答案解析

目录 一、单选题&#xff08;共15题&#xff0c;每题2分&#xff0c;共30分&#xff09; 二、判断题&#xff08;共10题&#xff0c;每题2分&#xff0c;共20分&#xff09; 三、编程题&#xff08;共4题&#xff0c;共50分&#xff09; 青少年软件编程&#xff08;图形化&a…

python与pycharm配置http服务

下载安装pycharm 下载pycharm 提取码&#xff1a;slgh 在任意自己工作的目录下创建两个文件夹&#xff0c;www文件夹及其目录下cgi-bin文件夹 自己的工作目录\www\cgi-bin 打开pycharm创建工程&#xff0c;选择www\cgi-bin目录 配置cgi&#xff0c;选择Run菜单&#xff0c;…

动漫制作技巧如何制作动漫视频

动漫制作技巧是很多新人想了解的问题&#xff0c;今天小编就来解答与大家分享一下动漫制作流程&#xff0c;为了帮助有兴趣的同学理解&#xff0c;大多数人会选择动漫培训机构&#xff0c;那么今天小编就带大家来看看动漫制作要掌握哪些技巧&#xff1f; 一、动漫作品首先完成…

MedNeRF:用于从单个X射线重建3D感知CT投影的医学神经辐射场

摘要 计算机断层扫描&#xff08;CT&#xff09;是一种有效的医学成像方式&#xff0c;广泛应用于临床医学领域&#xff0c;用于各种病理的诊断。多探测器CT成像技术的进步实现了额外的功能&#xff0c;包括生成薄层多平面横截面身体成像和3D重建。然而&#xff0c;这涉及患者暴…