You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WebFlux环境下实现带总记录数的分页查询API最佳实践

WebFlux实现带总记录数的分页查询最佳方案

在WebFlux里实现带总记录数的分页查询,最常用也最优雅的方案是封装一个包含分页数据和统计信息的响应DTO——毕竟Reactive栈里没有像Spring MVC那样现成的Page对象可以直接用,下面结合你的代码一步步来实现:

1. 定义通用分页响应DTO

首先创建一个通用的分页结果类,用来统一封装分页数据、总记录数、当前页、每页大小这些前端需要的核心信息:

public class PageResult<T> {
    private List<T> content;
    private long totalElements;
    private int pageNumber;
    private int pageSize;

    // 全参构造函数
    public PageResult(List<T> content, long totalElements, int pageNumber, int pageSize) {
        this.content = content;
        this.totalElements = totalElements;
        this.pageNumber = pageNumber;
        this.pageSize = pageSize;
    }

    // 省略getter和setter方法(必须添加,否则JSON序列化会出问题)
}

2. 修改Service层逻辑,并行查询数据和总数

WebFlux的核心是非阻塞异步,所以我们要并行执行数据查询和总数统计,用Mono.zip()来合并两个异步操作的结果,避免串行执行带来的性能损耗。

给你的shipmentService新增一个方法,返回包含分页信息的Mono<PageResult<ContainerNumber>>:

public Mono<PageResult<ContainerNumber>> getContainerNumbersWithTotal(String searchTerm, Pageable pageable) {
    // 1. 异步查询当前页的数据列表
    Mono<List<ContainerNumber>> contentMono = this.containerNumbers(searchTerm, pageable).collectList();
    // 2. 异步查询符合条件的总记录数(需要你根据持久层实现这个统计方法)
    Mono<Long> totalCountMono = countMatchingContainerNumbers(searchTerm);

    // 3. 合并两个异步结果,封装成PageResult
    return Mono.zip(contentMono, totalCountMono)
            .map(resultTuple -> new PageResult<>(
                    resultTuple.getT1(),
                    resultTuple.getT2(),
                    pageable.getPageNumber(),
                    pageable.getPageSize()
            ))
            // 保留你原来的空结果异常逻辑
            .flatMap(pageResult -> {
                if (pageResult.getContent().isEmpty()) {
                    return Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND, "No container numbers match your input"));
                }
                return Mono.just(pageResult);
            });
}

// 新增的统计总记录数方法示例(根据你的持久层框架调整)
private Mono<Long> countMatchingContainerNumbers(String searchTerm) {
    // 示例1:Reactive MongoDB实现
    // Query query = new Query(Criteria.where("containerNumber").regex(searchTerm));
    // return mongoTemplate.count(query, Shipment.class);
    
    // 示例2:R2DBC实现
    // return databaseClient.sql("SELECT COUNT(*) FROM shipments WHERE container_number LIKE :searchTerm")
    //         .bind("searchTerm", "%" + searchTerm + "%")
    //         .map(row -> row.get(0, Long.class))
    //         .one();
}

3. 修改Controller接口,返回分页结果

把原来返回Flux<ContainerNumber>的接口改成返回Mono<PageResult<ContainerNumber>>,同时更新Swagger注解说明:

@GetMapping(value = "/shipments/containernumbers")
@Operation(summary = "Returns a paginated list of related container numbers with total count", 
           description = "Return paginated container numbers along with total matching records based on input")
public Mono<PageResult<ContainerNumber>> containerNumbers(final String searchTerm, final Pageable pageable) {
    return shipmentService.getContainerNumbersWithTotal(searchTerm, pageable);
}

4. 前端接收示例

前端拿到的响应JSON结构会是这样的,直接用totalElements就能计算总页数,配合分页参数完成前端分页逻辑:

{
  "content": [{"containerNumber": "CNTR-123"}, {"containerNumber": "CNTR-456"}],
  "totalElements": 42,
  "pageNumber": 0,
  "pageSize": 10
}

关键注意点

  • 绝对不要用block()同步获取总数,这会阻塞Reactor线程,完全违背WebFlux的非阻塞设计。
  • 用Mono.zip()并行执行两个异步操作,能最大化利用Reactive的性能优势。
  • 这个DTO是通用的,可以复用在其他分页接口上,不用重复造轮子。

内容的提问来源于stack exchange,提问作者Sanchelz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 14:18:12