介绍
在本文中,我将向您展示如何在 Spring 或 Spring Boot 中使用键集分页技术。
虽然 Spring DataPagingAndSortingRepository提供的基于偏移量的默认分页在许多情况下很有用,但如果您必须迭代大型结果集,那么键集分页或查找方法技术可以提供更好的性能。
什么是键集分页
如本文所述,键集分页或查找方法允许我们在查找要加载的给定页面的第一个元素时使用索引。
加载最新 25 个实体的 Top-N 键集分页查询如下所示:Post
1
2
3
4
5
6
7
8
9
10
|
SELECT
id,
title,.8);"> created_on
FROM
post
ORDER BY
created_on DESC ,.8);"> id DESC
|
加载第二、第三或第 n 页的 Next-N 查询如下所示:
10
11
12
13
|
WHERE
(created_on,id) <
(:prevIoUsCreatedOn,:prevIoUsId)
如您所见,Keyset 分页查询是特定于数据库的,因此我们需要一个框架,该框架可以为我们提供抽象此功能的 API,同时为每个受支持的关系数据库生成适当的 sql 查询。
该框架称为Blaze Persistence,它支持JPA实体查询的Keyset Pagination。
如何在 Spring 中使用键集分页
使用 Spring 时,数据访问逻辑是使用 Spring 数据存储库实现的。因此,基本数据访问方法由 定义,并且自定义逻辑可以在一个或多个自定义 Spring 数据存储库类中抽象。JpaRepository

这是实体数据访问对象,它看起来像这样:PostRepository Post
4
|
@Repository
public interface PostRepository
extends JpaRepository<Post,Long>,CustomPostRepository {
}
|
如本文所述,如果我们想提供额外的数据访问方法,我们可以在定义自定义数据访问逻辑的地方进行扩展。PostRepository CustomPostRepository
外观如下:CustomPostRepository
12
|
public interface CustomPostRepository {
PagedList<Post> findTopN(
Sort sortBy,.8);"> int pageSize
);
PagedList<Post> findNextN(
Sort orderBy,.8);"> PagedList<Post> prevIoUsPage
实现接口的类如下所示:CustomPostRepositoryImpl CustomPostRepository
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
39
40
41
42
43
44
45
46
47
|
public class CustomPostRepositoryImpl
implements CustomPostRepository {
@PersistenceContext
private EntityManager entityManager;
@Autowired
private CriteriaBuilderFactory criteriaBuilderFactory;
@Override
public PagedList<Post> findTopN(
Sort sortBy,.8);"> int pageSize) {
return sortedCriteriaBuilder(sortBy)
.page( 0 ,pageSize)
.withKeysetExtraction( true )
.getResultList();
}
public PagedList<Post> findNextN(
PagedList<Post> prevIoUsPage) {
.page(
prevIoUsPage.getKeysetPage(),.8);"> prevIoUsPage.getPage() * prevIoUsPage.getMaxResults(),.8);"> prevIoUsPage.getMaxResults()
)
private CriteriaBuilder<Post> sortedCriteriaBuilder(
Sort sortBy) {
CriteriaBuilder<Post> criteriaBuilder = criteriaBuilderFactory
.create(entityManager,Post. class );
sortBy.forEach(order -> {
criteriaBuilder.orderBy(
);
});
return criteriaBuilder;
使用键集分页方法,如下所示:ForumService PostRepository
35
|
@Service
@Transactional (readOnly = true )
public class ForumService {
private PostRepository postRepository;
public PagedList<Post> firstLatestPosts(
return postRepository.findTopN(
Sort.by(
Post_.CREATED_ON
).descending().and(
Sort.by(
Post_.ID
).descending()
),.8);"> pageSize
);
public PagedList<Post> findNextLatestPosts(
return postRepository.findNextN(
}
|
测试时间
假设我们创建了 50 个实体:Post
19
|
LocalDateTime timestamp = LocalDateTime.of(
2021 , 12 , 30 , 0 , 0
);
LongStream.rangeClosed( 1 ,POST_COUNT).forEach(postId -> {
Post post = new Post()
.setId(postId)
.setTitle(
String.format(
"High-Performance Java Persistence - Chapter %d" ,.8);"> postId
)
)
.setCreatedOn(
Timestamp.valueOf(timestamp.plusMinutes(postId))
entityManager.persist(post);
});
|
加载第一页时,我们得到预期的结果:
PagedList<Post> topPage = forumService.firstLatestPosts(PAGE_SIZE);
assertEquals(POST_COUNT,topPage.getTotalSize());
assertEquals(POST_COUNT / PAGE_SIZE,topPage.getTotalPages());
assertEquals( 1 ,topPage.getPage());
List<Long> topIds = topPage.stream().map(Post::getId).toList();
assertEquals(Long.valueOf( 50 ),topIds.get( 0 ));
assertEquals(Long.valueOf( 49 ),topIds.get( 1 ));
|
而且,在Postgresql上执行的SQL查询如下所示:
17
|
p.id AS col_0_0_,.8);"> p.created_on AS col_1_0_,.8);"> p.id AS col_2_0_,.8);"> (
SELECT count (*)
FROM post post1_
) AS col_3_0_,.8);"> p.id AS id1_0_,.8);"> p.created_on AS created_2_0_,.8);"> p.title AS title3_0_
post p
p.created_on DESC ,.8);"> p.id DESC
LIMIT 25
|
加载第二页时,我们得到下一个最新的 25 个实体:Post
8
|
PagedList<Post> nextPage = forumService.findNextLatestPosts(topPage);
assertEquals( 2 ,nextPage.getPage());
List<Long> nextIds = nextPage.stream().map(Post::getId).toList();
assertEquals(Long.valueOf( 25 ),nextIds.get( 0 ));
assertEquals(Long.valueOf( 24 ),nextIds.get( 1 ));
|
底层 sql 查询如下所示:
20
|
(p.created_on,p.id) <
( '2021-12-30 12:26:00.0' ,26) AND 0=0
很酷,对吧?
结论
键集分页在实现无限滚动解决方案时非常有用,虽然 Spring Data 中没有内置支持它,但我们可以使用 Blaze Persistence 和自定义 Spring 数据存储库轻松地自己实现它。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。
|
|
|
|