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

在线演示

演示地址: https://admin.metalite.top/

演示账号: guess

演示密码: admin@2026

相关推荐
元界metalite20 分钟前
SpringBoot整合Redis分布式锁-为什么不能直接DEL
后端
晚安code24 分钟前
LangChain4j AiService 实战:会话记忆与结构化输出
java·langchain
Zane199428 分钟前
你以为的性能瓶颈,未必是真的瓶颈:用 cProfile 找到真凶
后端·python
晚安code30 分钟前
LangChain4j 实战:RAG、工具调用、护轨与 SSE 流式输出
java·langchain
请你吃div30 分钟前
Node 后端项目 Docker 自动部署教程(GitHub + 宝塔 + Self-hosted Runner)
后端·docker·node.js
郑州光合科技余经理1 小时前
本地生活平台搭建:统一订单表与多后台切换怎么拆
java·开发语言·前端·系统架构·uni-app·php·ai编程
Lovefoolself1 小时前
Electron框架使用vue开发跨平台桌面工具应用-后台日志发送到前台和执行导入ZIP
后端
杰哥哥不是个好叔叔1 小时前
【Spring AI】Spring AI 2.0.1 新特性袭来
java·人工智能·spring
莫陌尛.2 小时前
Java核心知识点:直接内存、CPU密集型线程池、并行流对比文档
java·开发语言