您可以将 JPA 分页用于实体查询和本机 SQL。
为了限制底层查询ResultSet
大小,JPAQuery
接口提供了setMaxResults
方法.
浏览下一页需要将结果集定位到最后一页结束的位置。为此,JPAQuery
接口提供了setFirstResult
方法。
JPQL
List<Post> posts = entityManager.createQuery("""
select p
from Post p
order by p.createdOn
""", Post.class)
.setFirstResult(10)
.setMaxResults(10)
.getResultList();
DTO 投影查询
JPA 查询分页不限于仅返回实体的实体查询。您也可以将它用于 DTO 投影。
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();
本机 SQL 查询
JPA 查询分页不限于实体查询,例如 JPQL 或 Criteria API。您也可以将其用于本机 SQL 查询。
List<Post> posts = entityManager.createQuery("""
select p
from Post p
left join fetch p.comments
where p.title like :titlePattern
order by p.createdOn
""", Post.class)
.setParameter("titlePattern", "High-Performance Java Persistence %")
.setMaxResults(5)
.getResultList();
JOIN FETCH 和分页
但是,如果我们尝试JOIN FETCH
在实体查询中使用该子句,同时还使用 JPA 分页:
List<Post> posts = entityManager.createQuery("""
select p
from Post p
left join fetch p.comments
where p.title like :titlePattern
order by p.createdOn
""", Post.class)
.setParameter("titlePattern", "High-Performance Java Persistence %")
.setMaxResults(5)
.getResultList();
Hibernate 将发出以下警告消息:
HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
并且执行的 SQL 查询将缺少分页子句:
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
WHERE p.title LIKE :titlePattern
ORDER BY p.created_on
这是因为 Hibernate 想要完全获取实体以及它们的集合,如JOIN FETCH
子句所示,而 SQL 级别的分页可能会截断可能离开集合中元素较少ResultSet
的父实体。Post
comments
警告的问题HHH000104
是 Hibernate 将获取Post
和PostComment
实体的乘积,并且由于结果集的大小,查询响应时间会很长。
为了解决此限制,您必须使用窗口函数查询:
@NamedNativeQuery(
name = "PostWithCommentByRank",
query = """
SELECT *
FROM (
SELECT
*,
DENSE_RANK() OVER (
ORDER BY "p.created_on", "p.id"
) rank
FROM (
SELECT
p.id AS "p.id", p.created_on AS "p.created_on",
p.title AS "p.title", pc.post_id AS "pc.post_id",
pc.id as "pc.id", pc.created_on AS "pc.created_on",
pc.review AS "pc.review"
FROM post p
LEFT JOIN post_comment pc ON p.id = pc.post_id
WHERE p.title LIKE :titlePattern
ORDER BY p.created_on
) p_pc
) p_pc_r
WHERE p_pc_r.rank <= :rank
""",
resultSetMapping = "PostWithCommentByRankMapping"
)
@SqlResultSetMapping(
name = "PostWithCommentByRankMapping",
entities = {
@EntityResult(
entityClass = Post.class,
fields = {
@FieldResult(name = "id", column = "p.id"),
@FieldResult(name = "createdOn", column = "p.created_on"),
@FieldResult(name = "title", column = "p.title"),
}
),
@EntityResult(
entityClass = PostComment.class,
fields = {
@FieldResult(name = "id", column = "pc.id"),
@FieldResult(name = "createdOn", column = "pc.created_on"),
@FieldResult(name = "review", column = "pc.review"),
@FieldResult(name = "post", column = "pc.post_id"),
}
)
}
)
有关使用窗口函数解决HHH000104
问题的更多详细信息以及DistinctPostResultTransformer
.