Spring Webflux + r2dbc 分页查询 示例1
1. 概述
本小结主要区分分页的基本概念, 如果已经熟悉分页的原理的同学可以忽略本小结
2. 分页原理
本例中, 我们将使用 offset + page 的方式进行分页, 那么如何计算page和offset之间的关系呢? 假设我们有11条记录, 比如emp_talbe:
+--------+--------------+------------+--------+---------+
| id | name | hire_date | salary | dept_id |
+--------+--------------+------------+--------+---------+
| 1 | Hunt | 2001-05-01 | 5000 | 4 | // page 0 , offset = 1
| 2 | Tony | 2002-07-15 | 6500 | 1 |
| 3 | Sarah | 2005-10-18 | 8000 | 5 |
| 4 | Rick | 2007-01-03 | 7200 | 3 |
| 5 | Martin | 2008-06-24 | 5600 | NULL |
| 6 | Huntte | 2001-05-01 | 5000 | 4 |
| 7 | Montana | 2002-07-15 | 6500 | 1 |
| 8 | Connor | 2005-10-18 | 8000 | 5 |
| 9 | Deckard | 2007-01-03 | 7200 | 3 |
| 10 | Blank | 2008-06-24 | 5600 | NULL |
| 11 | Malank | 2008-06-24 | 5600 | NULL |
+--------+--------------+------------+--------+---------+
在我们梳理各种关系前, 我们先确认一些概念
-
按page 的方式进行分页
比如上面的例子, 我们有11条记录, 如果我们假设pagesize为5, page0 为起始页, 那么page就等于3, 分别是page0, page1, page2
page: 页数, 第几页
pagesize: 每一页记录的条目数 -
按sql查询条件的方式进行分页
还是上面的例子, 假设offset为5, limit 为5 如果我们执行如下查询
select * from emp_talbe offset 5 limit 5
我们将得到 6 | Huutte -> 10 | Blank 的五条记录. 那么可以看出
offset: 游标, 查询的起始点
limit: 限定查询记录个数
那么此时我们是希望通过使用 offset + limit 的sql 来进行分页查询, 那么假设我们的希望pagesize 为5, 那么可以简单理解为我们的limit也就是5. 那么如果我们希望查询第0页的记录page0, 那么 offset = 0, limit=5, 查询第1页的记录page1, 那么 offset = 5, limit=5
// Page 0 : select * from emp_talbe offset 0 limit 5
// Page 1 : select * from emp_talbe offset 5 limit 5
// Page 2 : select * from emp_talbe offset 10 limit 5
所以自然, 我们可以得出以下我们可能需要用到值的关系
- limit = pagesize
- pageNumber = offset/limit 取整
- offset = pageNumber * limit
- nextPage number = offset + limit
- hasPrevious = ( offset - limit >= 0 )
- previousPageNumber = hasPrevious ? offset-limit : 0
2. Spring R2dbc 对分页的支持
通过Spring R2dbc的官方文档: https://docs.spring.io/spring-data/r2dbc/docs/1.1.0.RELEASE/reference/html/#r2dbc.core
我们可以了解到, 我们可以使用 Pageable 对象进行分页查询. 例如:
Flux<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable);
那么接下来我们可以尝试使用这样的方式进行分页查询.
3. Pageable 接口
查看源码, 可以看出此接口涵盖了我们基本所需的分页常见方法, 这些方法也在上一步中,我们分析出了我们需要使用的值:
public interface Pageable {
/**
* Returns a {@link Pageable} instance representing no pagination setup.
*
* @return
*/
static Pageable unpaged() {
return Unpaged.INSTANCE;
}
/**
* Returns whether the current {@link Pageable} contains pagination information.
*
* @return
*/
default boolean isPaged() {
return true;
}
/**
* Returns whether the current {@link Pageable} does not contain pagination information.
*
* @return
*/
default boolean isUnpaged() {
return !isPaged();
}
/**
* Returns the page to be returned.
*
* @return the page to be returned.
*/
int getPageNumber();
/**
* Returns the number of items to be returned.
*
* @return the number of items of that page
*/
int getPageSize();
/**
* Returns the offset to be taken according to the underlying page and page size.
*
* @return the offset to be taken
*/
long getOffset();
/**
* Returns the sorting parameters.
*
* @return
*/
Sort getSort();
/**
* Returns the current {@link Sort} or the given one if the current one is unsorted.
*
* @param sort must not be {@literal null}.
* @return
*/
default Sort getSortOr(Sort sort) {
Assert.notNull(sort, "Fallback Sort must not be null!");
return getSort().isSorted() ? getSort() : sort;
}
/**
* Returns the {@link Pageable} requesting the next {@link Page}.
*
* @return
*/
Pageable next();
/**
* Returns the previous {@link Pageable} or the first {@link Pageable} if the current one already is the first one.
*
* @return
*/
Pageable previousOrFirst();
/**
* Returns the {@link Pageable} requesting the first page.
*
* @return
*/
Pageable first();
/**
* Returns whether there's a previous {@link Pageable} we can access from the current one. Will return
* {@literal false} in case the current {@link Pageable} already refers to the first page.
*
* @return
*/
boolean hasPrevious();
/**
* Returns an {@link Optional} so that it can easily be mapped on.
*
* @return
*/
default Optional<Pageable> toOptional() {
return isUnpaged() ? Optional.empty() : Optional.of(this);
}
}
3. Pageable 接口的实现类 PageRequest
一个比较常用的实现类就是 PageRequest, 那么它实现了很多我们需要的方法, 同时提供of 静态方法供我们调用创建PageRequest, 例如
/**
* Creates a new unsorted {@link PageRequest}.
*
* @param page zero-based page index, must not be negative.
* @param size the size of the page to be returned, must be greater than 0.
* @since 2.0
*/
public static PageRequest of(int page, int size) {
return of(page, size, Sort.unsorted());
}
/**
* Creates a new {@link PageRequest} with sort parameters applied.
*
* @param page zero-based page index.
* @param size the size of the page to be returned.
* @param sort must not be {@literal null}, use {@link Sort#unsorted()} instead.
* @since 2.0
*/
public static PageRequest of(int page, int size, Sort sort) {
return new PageRequest(page, size, sort);
}
/**
* Creates a new {@link PageRequest} with sort direction and properties applied.
*
* @param page zero-based page index, must not be negative.
* @param size the size of the page to be returned, must be greater than 0.
* @param direction must not be {@literal null}.
* @param properties must not be {@literal null}.
* @since 2.0
*/
public static PageRequest of(int page, int size, Direction direction, String... properties) {
return of(page, size, Sort.by(direction, properties));
}
那么我们比如要查询第8页, 限定每页10个记录, 我们只需使用
PageRequest.of(8, 10);
使用在Repository 的方法中:
MyRepository extends ReactiveCrudRepository<Employee, UUID>{
...
Flux<Employee> getEmployeebyGender((@Param("gender") String gender, Pageable pageable)
...
}
//调用
MyRepository.getEmployeebyGender("man", PageRequest.of(8, 10) )
此时, 我们已经可以使用现有的实现类满足我们的需求, 但是我们需要使用使用 offset 进行查询, 那么接下来我们将要讨论如何结合自己的情况来调整开发