当前位置: 首页 > 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 数据存储库轻松地自己实现它。

 

相关文章:

  • 做品牌特价的网站/山西网络推广
  • 网站上的招牌图怎么做/设计网站的公司
  • 在哪些网站上申请做广告可以在百度引擎能收到关键字/深圳网络推广系统
  • 扬中网站推广价格/无锡网络优化推广公司
  • 织梦网站排版能调整吗/什么是sem推广
  • 北京建设招标网站/啦啦啦资源视频在线观看8
  • gcd区间 (ST表)(爱思创算法四)
  • 轻量化神经网络(移动设备上的神经网络)的整体框架
  • React基础汇总
  • WEB前端网页设计 HTML CSS 网页设计参数 - 【浮动与定位】
  • Docker:rabbitmq启动镜像后访问15672端口无法显示管理界面问题解决
  • 【论文精读8】MVSNet系列论文详解-UCS-Net
  • 大数据hadoop_HDFS概述(1)
  • Java多线程之:队列同步器AbstractQueuedSynchronizer原理剖析
  • 用ACL实现防火墙功能
  • MySQL 使用触发器记录用户的操作日志
  • Secureboot概念
  • ARM汇编之程序状态寄存器传输指令