当前位置: 首页 > news >正文

如何在 Spring 或 Spring Boot 中使用键集分页

介绍

在本文中,我将向您展示如何在 Spring 或 Spring Boot 中使用键集分页技术。

虽然 Spring DataPagingAndSortingRepository提供的基于偏移量的默认分页在许多情况下很有用,但如果您必须迭代大型结果集,那么键集分页或查找方法技术可以提供更好的性能。

什么是键集分页

如本文所述,键集分页或查找方法允许我们在查找要加载的给定页面的第一个元素时使用索引。

加载最新 25 个实体的 Top-N 键集分页查询如下所示:Post

1
2
3
4
5
6
7
8
9
10
SELECT
    id,
    title,
    created_on
FROM
    post
ORDER BY
    created_on DESC,
    id DESC
FETCH FIRST 25 ROWS ONLY

加载第二、第三或第 n 页的 Next-N 查询如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT
    id,
    title,
    created_on
FROM
    post
WHERE
  (created_on, id) <
  (:previousCreatedOn, :previousId)
ORDER BY
    created_on DESC,
    id DESC
FETCH FIRST 25 ROWS ONLY

如您所见,Keyset 分页查询是特定于数据库的,因此我们需要一个框架,该框架可以为我们提供抽象此功能的 API,同时为每个受支持的关系数据库生成适当的 SQL 查询。

该框架称为Blaze Persistence,它支持JPA实体查询的Keyset Pagination。

如何在 Spring 中使用键集分页

使用 Spring 时,数据访问逻辑是使用 Spring 数据存储库实现的。因此,基本数据访问方法由 定义,并且自定义逻辑可以在一个或多个自定义 Spring 数据存储库类中抽象。JpaRepository

这是实体数据访问对象,它看起来像这样:PostRepositoryPost

1
2
3
4
@Repository
public interface PostRepository
        extends JpaRepository<Post, Long>, CustomPostRepository {
}

如本文所述,如果我们想提供额外的数据访问方法,我们可以在定义自定义数据访问逻辑的地方进行扩展。PostRepositoryCustomPostRepository

外观如下:CustomPostRepository

1
2
3
4
5
6
7
8
9
10
11
12
public interface CustomPostRepository {
 
    PagedList<Post> findTopN(
        Sort sortBy,
        int pageSize
    );
 
    PagedList<Post> findNextN(
        Sort orderBy,
        PagedList<Post> previousPage
    );
}

实现接口的类如下所示:CustomPostRepositoryImplCustomPostRepository

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
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,
            int pageSize) {
        return sortedCriteriaBuilder(sortBy)
            .page(0, pageSize)
            .withKeysetExtraction(true)
            .getResultList();
    }
 
    @Override
    public PagedList<Post> findNextN(
            Sort sortBy,
            PagedList<Post> previousPage) {
        return sortedCriteriaBuilder(sortBy)
            .page(
                previousPage.getKeysetPage(),
                previousPage.getPage() * previousPage.getMaxResults(),
                previousPage.getMaxResults()
            )
            .getResultList();
    }
 
    private CriteriaBuilder<Post> sortedCriteriaBuilder(
            Sort sortBy) {
        CriteriaBuilder<Post> criteriaBuilder = criteriaBuilderFactory
            .create(entityManager, Post.class);
             
        sortBy.forEach(order -> {
            criteriaBuilder.orderBy(
                order.getProperty(),
                order.isAscending()
            );
        });
         
        return criteriaBuilder;
    }
}

使用键集分页方法,如下所示:ForumServicePostRepository

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
@Service
@Transactional(readOnly = true)
public class ForumService {
 
    @Autowired
    private PostRepository postRepository;
 
    public PagedList<Post> firstLatestPosts(
            int pageSize) {
        return postRepository.findTopN(
            Sort.by(
                Post_.CREATED_ON
            ).descending().and(
                Sort.by(
                    Post_.ID
                ).descending()
            ),
            pageSize
        );
    }
 
    public PagedList<Post> findNextLatestPosts(
            PagedList<Post> previousPage) {
        return postRepository.findNextN(
            Sort.by(
                Post_.CREATED_ON
            ).descending().and(
                Sort.by(
                    Post_.ID
                ).descending()
            ),
            previousPage
        );
    }
}

测试时间

假设我们创建了 50 个实体:Post

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
LocalDateTime timestamp = LocalDateTime.of(
    2021123012000
);
 
LongStream.rangeClosed(1, POST_COUNT).forEach(postId -> {
    Post post = new Post()
    .setId(postId)
    .setTitle(
        String.format(
            "High-Performance Java Persistence - Chapter %d",
            postId
        )
    )
    .setCreatedOn(
        Timestamp.valueOf(timestamp.plusMinutes(postId))
    );
 
    entityManager.persist(post);
});

加载第一页时,我们得到预期的结果:

1
2
3
4
5
6
7
8
9
10
11
12
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查询如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SELECT
    p.id AS col_0_0_,
    p.created_on AS col_1_0_,
    p.id AS col_2_0_,
    (
        SELECT count(*)
        FROM post post1_
    AS col_3_0_,
    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 DESC,
    p.id DESC
LIMIT 25

加载第二页时,我们得到下一个最新的 25 个实体:Post

1
2
3
4
5
6
7
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 查询如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SELECT
    p.id AS col_0_0_,
    p.created_on AS col_1_0_,
    p.id AS col_2_0_,
    (
        SELECT count(*)
        FROM post post1_
    AS col_3_0_,
    p.id AS id1_0_,
    p.created_on AS created_2_0_,
    p.title AS title3_0_
FROM
    post p
WHERE
    (p.created_on, p.id) <
    ('2021-12-30 12:26:00.0', 26) AND 0=0
ORDER BY
    p.created_on DESC,
    p.id DESC
LIMIT 25

很酷,对吧?

结论

键集分页在实现无限滚动解决方案时非常有用,虽然 Spring Data 中没有内置支持它,但我们可以使用 Blaze Persistence 和自定义 Spring 数据存储库轻松地自己实现它。

 

相关文章:

  • Nginx配置实例-负载均衡
  • Windows内核--子系统(3.5)
  • conductor cluster server decision处理冲突解决方法
  • Windows OpenGL ES 图像绿幕抠图
  • Java数据结构与Java算法学习Day03---线性表(简略笔记记录)
  • Vue中常用的指令
  • CycloneDDS配置详细说明中文版(四)
  • 软件定义汽车的关键—车载操作系统
  • Docker consul的容器服务更新与发现
  • 【第七章 MySQL体系结构、存储引擎、InnoDB、MyISAM、Memory、存储引擎特点及选择】
  • 【Transformer】Transformer理论知识
  • Eyeshot 2023 预期计划 Eyeshot 2023 即将见面
  • 数据结构的各种排序
  • 游戏开发36课 cocoscreator scrollview优化
  • 【Python自然语言处理】文本向量化的六种常见模型讲解(独热编码、词袋模型、词频-逆文档频率模型、N元模型、单词-向量模型、文档-向量模型)
  • G1D30-NLP(Tokenizer)DP(交叠子问题)
  • 513.找树左下角的值
  • Java 实例:删除字符串中的一个字符和字符串替换
  • Elasticsearch面试题
  • PCB入门介绍与电阻电容电感类元件的创建
  • Webpack完整打包流程分析
  • ubuntu 20.04 设置DNS
  • 小型扫雪机结构设计
  • Day17 | 每天五道题
  • 无人机边缘计算中的计算卸载——Stackelberg博弈方法论文复现附matlab代码
  • 【明天截稿】Wiley出版社,传感器网络与电路类SCI,仅2-3个月左右录用
  • Matplotlib学习笔记(第一章)
  • 做一个公司网站大概要多少钱?
  • MySQL 数据库主从复制
  • 分布式文件存储系统FastDFS[1]-介绍以及安装
  • 黑龙江2022艺术类高职专科批次A段院校投档分数线汇总
  • 药品生物技术专业前景怎么样 好不好就业
  • 2022考研准考证打印入口开通 在哪打印
  • 2022交通运输类专业有哪些 就业方向有什么
  • 2022年云南高考525分能报什么大学 525分能上哪些院校
  • 2022吉林师范大学博达学院寒假放假时间公布 几号开始放寒假
  • 南京传媒学院是一本还是二本
  • 湖南2021本科批(普通类历史类)第一次征集志愿投档分数线
  • 2022年甘肃高考482分能报什么大学 482分能上哪些院校
  • 2022年全国各大高校在山东招生计划及分数