Spring Webflux + r2dbc 分页查询 示例2
1. 现有API 对offset 的支持
我们可以看到, 如果使用R2dbc 已经实现的分页, 那么我们可以使用Pageable 进行分页查询, 但是如果我们需要使用offset 此时, 现有的支持也许并不够用, 那么我们需要自己来实现 Pageable 接口来实现offset分页
2. 为什么要使用offset 来进行分页
由于项目需要, 决策层决定只给API调用者提供获取下一页的功能, 而不是提供给API 调用者提供直接查询某一页的功能, 我个人理解是, 这样做可以直接让API调用方调用指定的需要的查询记录的位置而不是必须要页首.
3. 具体实现
@Value
public class Offset implements Pageable {
int offset;
int limit;
//避免重用 isPaged
boolean pagingApplied;
@Override
public int getPageNumber() {
return offset / limit;
}
@Override
public int getPageSize() {
return limit;
}
@Override
public long getOffset() {
return offset;
}
@Override
public Sort getSort() {
//这里暂时不进行排序
return Sort.unsorted();
}
@Override
public Pageable next() {
if (!pagingApplied){
return unpaged();
}
return new Offset(offset + limit, limit, true);
}
@Override
public Pageable previousOrFirst() {
if (!pagingApplied){
return unpaged();
}
//这里都返回 offset对象
return hasPrevious() ? new Offset(offset - limit, limit, true) : first();
}
@Override
public Pageable first() {
if (!pagingApplied){
return unpaged();
}
return new Offset(0, limit, true);
}
@Override
public boolean hasPrevious() {
return offset >= limit;
}
public static Offset unpaged() {
return new Offset(0, 0, false);
}
@Override
public boolean isPaged() {
return pagingApplied;
}
}
这里我们就已经实现了所有需要的方法, 那么我们可以使用我们的实现来使用 Offset 类来进行分页查询, 这里笔者觉得如果Spring Pageable 提供泛型会更加灵活, 这样的话我们直接可以指定返回类型, 不过也有可能Spring 本身有其他考量在此.
4. 使用Offset 类
4.1 使用方法和之前的使用方法一致, 那么我们如果定义了一个Repository 接口
MyRepository extends ReactiveCrudRepository<Employee, UUID>{
...
Flux<Employee> getEmployeebyGender((@Param("gender") String gender, Pageable pageable)
...
}
4.2 调用
... ...
myRepository.getEmployeebyGender("man", new Offset(8, 10, true) )
4.3 构造函数
public Offset(int offset, int limit, boolean pagingApplied) {
this.offset = Math.max(offset, 0);
this.limit = Math.max(limit, 0);
this.pagingApplied = pagingApplied;
}
public Offset(int offset, int limit) {
this(offset, limit, true);
}
但是这里还是有缺陷的, 如果外部调用构造函数, 例如 new Offset(5, 10, false), 那么势必,我们使用 pageable.isPaged() 会得到false, 那么我们接下来很可能会碰到一些问题, 但很显然我们需要使用new Offset(5, 10, true). 那么这里最后将构造函数私有, 然后提供,比如一个of的静态方法来让外部使用:
public static Offset of(int offset, Integer limit) {
//加入一些判断逻辑
return new Offset(offset, limit, true);
}
5. 遗留的问题
其实这里我们依然有一些问题存在, 例如, 如果控制层调用时, 提供的参数为空的情况, 我们就比较被动了, 以为我们使用的是int 型, 不允许为null, 所以有一种情况, 假设用户请求为: GET "/dosomting?limit=10", 此时, 我们获得的offset是空, 那么我们可能想设定offset = 0, 那么如果使用int 就办不到了, 所以我们需要 offset 为 Integer, 或者给offset 一个默认值等.
6. 遗留的问题
由于时间有限, 笔者没有进行过多的测试, 可能实现类中还有隐含问题待解决, 不过自己实现接口往往会遇到各种问题, 这也是很难避免的, 一般建议, 如果依赖包本身含有现成的实现类, 那么我们最好不要重复造轮子, 但是如果必须自己实现, 那良好的测试是必要的