SpringBoot分页接口怎么设计-pageSize不设上限会发生什么

Spring Boot 分页接口怎么设计:pageSize 不设上限会发生什么

工程判断: 分页接口通常只需要 pageNum 和 pageSize 两个参数,但 pageSize 一旦没有上限,分页就可能退化成一次全表读取,并把数据库、网络和 JSON 序列化同时拖入慢请求。

这类风险不能依靠前端"正常传值"来避免,因为脚本、旧客户端和恶意请求都可以绕过页面。企业级分页契约必须统一默认值、最大值、排序稳定性和总数查询成本。

MetaLite 将分页参数校验和 DAO 查询模型收敛到公共入口,避免每个接口重复判断。本文先给出通用分页防线,再结合参数对象与查询代码说明限制应该落在哪一层。

一、pageSize 为什么必须有服务端上限

如果前端把每页大小从 20 改成 200000,一次请求会同时放大:

  • 数据库扫描和结果集内存;
  • JDBC 对象创建;
  • Entity/DTO 转换;
  • JSON 序列化;
  • 网关与服务间网络传输;
  • 接口日志和前端渲染。

前端下拉框限制不是安全边界,调用方可以绕过页面直接请求。上限必须在服务端契约中校验。

MetaLite 默认最多 100 条,是明确的容量保护,而不是由每个列表接口自行决定是否检查。

二、为什么 pageNo 也设置最大值

pageNo <= 1000 可以避免无限深翻页,但它只是保护线,不是深分页优化方案。

传统 offset 分页越往后,数据库可能需要跳过越多记录。真实的大数据列表更适合:

  • 基于稳定排序键的游标分页;
  • Elasticsearch search_after
  • 限制可查询时间范围;
  • 异步导出而不是无限翻页。

最大页码能拒绝极端请求,却不能让第 1000 页变得便宜。

三、业务查询参数为什么继承分页基类

QueryUserParam 继承 PageQueryParam,再增加用户名、手机号、状态和组织条件:

java 复制代码
public class QueryUserParam extends PageQueryParam {
    private String userName;
    private String phone;
    private Integer status;
}

这样所有分页查询共享页码契约,业务字段仍保持类型明确。比使用一个 Map<String,Object> 更容易生成 OpenAPI 文档、执行 Bean Validation 和进行字段重构。

四、分页返回为什么统一为 total 加 list

PageResultDto<E> 只包含:

java 复制代码
private long total;
private List<E> list;

前端可以根据 total 计算页数,列表数据又保持泛型类型。统一结构也让 InternalServiceClient.callOneInstanceRtnPageData 能按元素类型恢复 RPC 返回值。

是否额外返回 pageNopageSizehasNext,取决于客户端需求。当前源码不返回这些字段,调用方需要保留自己的请求参数。

五、查询为什么先 count 再查列表

SysUserService.listUser 的流程是:

text 复制代码
构造 Criteria
  → countByCriteria
  → total 为 0 时直接返回
  → 创建 Query 与排序
  → Pageable(pageNo, pageSize)
  → findListByQuery

先 count 可以避免无数据时继续查询列表,也满足精确页数展示。代价是每次分页通常执行两条 SQL;在高吞吐或复杂查询中,count 可能比列表本身更贵。

对于"加载更多"场景,可以查询 pageSize + 1 条只判断 hasNext,不一定需要精确 total。

六、稳定排序是分页正确性的前提

源码按更新时间倒序:

java 复制代码
query.orderBy(OrderBy.desc(SysUserEntity::getUpdateTime));

如果多个记录更新时间相同,仅按这一列排序可能出现跨页重复或遗漏。更稳妥的排序应追加唯一键:

text 复制代码
ORDER BY update_time DESC, id DESC

游标分页更需要稳定、唯一且方向一致的排序键。

分页不只是加 limit,还要定义数据变化时的顺序语义。

七、当前空结果还有一个契约细节

PageResultDto 默认构造时 list 为 null。部分 Service 在 total 为 0 时直接返回,因此调用方可能收到:

json 复制代码
{"total":0,"list":null}

很多前端更希望固定收到空数组:

json 复制代码
{"total":0,"list":[]}

统一空集合能减少调用方判空分支。当前实现尚未在 DTO 默认值上保证这一点,文章不能把它描述成已经完成的能力。

八、分页校验必须确保嵌套参数被触发

公网请求的业务参数先以明文或密文进入网关,解密后才生成内部 bizParam。真正的 @Min/@Max 校验必须发生在反序列化后的业务对象上。

MetaLite 的 InternalBizParamReqbizParam 使用 @NotNull @Valid,并由入口参数处理器读取校验结果。缺少嵌套 @Valid 时,分页上限写在类上也可能完全不执行。

九、一套分页接口验收清单

发布前至少检查:

  1. pageNo、pageSize 是否有默认值、下限和上限;
  2. 排序字段是否稳定并包含唯一键;
  3. 深分页是否需要游标或时间范围;
  4. total 是否真的被业务需要;
  5. 空结果统一返回 null 还是空数组;
  6. 嵌套 Bean Validation 是否执行;
  7. 返回 Entity 是否可能暴露敏感字段;
  8. 大页请求是否会放大接口日志。

MetaLite 已把分页范围和通用返回结构放入工程基座。真正成熟的分页还需要结合数据库规模、排序稳定性和客户端交互继续设计。

十、把分页边界写成可重复执行的契约测试

分页接口至少要固定以下输入和结果:

用例 输入 应验证的行为
正常首页 pageNo=1,pageSize=20 返回稳定排序的前 20 条,total 与同条件 count 一致
超大页容量 pageSize 超过上限 在进入 DAO 前直接校验失败
非法页码 0、负数或超过约定上限 返回明确参数错误,不执行全表查询
删除中间数据 连续请求两页期间删除一条记录 明确接受偏移分页漂移,或改用游标方案
同排序值 多条记录业务排序相同 必须追加唯一字段排序,避免跨页重复

只验证返回 JSON 结构还不够。测试需要同时观察 SQL 是否带 LIMIT、count 与 list 是否使用同一 Criteria,以及空结果时 total/list 的契约是否稳定。


框架简介 MetaLite 是面向企业生产环境的新一代 Java 微服务技术底座。系列文章重点分享代码背后的设计思路、技术取舍与工程实践。

源码基线 JDK 21、Spring Boot 3.2.9、Spring Cloud 2023.0.1、Spring Cloud Alibaba 2023.0.1.3,具体组件版本以项目 backend-bom 为准。

作者简介 15 年 Spring 体系企业级开发经验,专注于 Java 微服务架构、工程治理与生产实践。

持续更新 MetaLite 系列内容将持续更新,围绕核心设计、源码链路、技术取舍与生产实践展开。欢迎关注作者,及时获取后续内容。

在线演示 演示地址: admin.metalite.top/ 演示账号: guess 演示密码: admin@2026

相关推荐
IT爱学堂2 小时前
Go开发疑难杂症终结者通关指南
后端
大勇前进2 小时前
从10分钟到10秒:一个真实慢查询的SQL Server优化全记录
后端
站大爷IP2 小时前
被Python异步坑了三年,才搞懂await和create_task的区别
后端
大白802 小时前
你的数据库密码还在用明文?SQL Server 凭据管理的 3 种现代方案
后端
凤山老林2 小时前
Spring Boot 集成 ShardingSphere-Encrypt 实现字段级实时脱敏
java·spring boot·后端·数据脱敏
vipxieliang2 小时前
ValidX v1.2.0 更新日志
java·后端
Zane19943 小时前
只改一个方向的引用,循环引用就能立刻被回收?一文讲透 weakref 弱引用
后端·python
长大19883 小时前
窗口函数用不好反而更慢?SQL Server中OVER子句的4个性能陷阱
后端
大黄评测3 小时前
MERGE语句有Bug?SQL Server官方不推荐的背后真相与替代方案
后端