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
相关产品推荐
相关产品推荐

