【项目扩展实战|第8篇】Spring Boot + MyBatis 分页条件查询接口优化
前言前面我们已经写过商品详情缓存、文件上传等功能。这一篇继续回到后台管理系统里非常高频的一个接口分页条件查询。只要是后台列表页面基本都离不开分页查询。比如商品列表分页查询用户列表分页查询订单列表分页查询操作日志分页查询文件记录分页查询如果只是查全部数据接口很简单但真实后台一般都需要支持条件筛选例如商品名称、状态、价格区间、创建时间范围等。这一篇我们就以商品列表为例实现一个完整的分页条件查询接口并顺带优化参数对象、分页返回结构和 MyBatis 动态 SQL。一、分页条件查询的整体需求我们要实现的接口是GET /admin/product/page支持这些查询条件当前页码page每页条数pageSize商品名称关键字keyword商品状态status最低价格minPrice最高价格maxPrice创建开始时间beginTime创建结束时间endTime返回结果大概是{code:200,message:操作成功,data:{total:100,rows:[]}}这里使用total rows的结构前端分页组件比较容易使用。二、准备分页返回对象先定义一个通用分页返回类。packagecom.example.common;importlombok.AllArgsConstructor;importlombok.Data;importlombok.NoArgsConstructor;importjava.util.List;DataNoArgsConstructorAllArgsConstructorpublicclassPageResultT{privateLongtotal;privateListTrows;}文字说明PageResult是一个通用类不只商品列表能用用户列表、订单列表、日志列表也都能用。其中total表示总记录数rows表示当前页数据前端拿到total后就可以计算总页数拿到rows后就可以渲染表格。三、添加 PageHelper 依赖分页可以自己写limit也可以使用 PageHelper。这里使用 PageHelper因为它和 MyBatis 配合比较方便。dependencygroupIdcom.github.pagehelper/groupIdartifactIdpagehelper-spring-boot-starter/artifactIdversion1.4.7/version/dependency文字说明PageHelper 的核心用法是PageHelper.startPage(page,pageSize);ListProductVOlistproductMapper.selectPage(queryDTO);PageProductVOpageResult(PageProductVO)list;它会自动帮我们在 SQL 后面加分页语句并统计总记录数。四、准备查询参数对象不要把所有查询参数都散落在 Controller 方法参数里更推荐封装成一个 DTO。packagecom.example.dto;importlombok.Data;importorg.springframework.format.annotation.DateTimeFormat;importjava.math.BigDecimal;importjava.time.LocalDateTime;DatapublicclassProductPageQueryDTO{privateIntegerpage1;privateIntegerpageSize10;privateStringkeyword;privateIntegerstatus;privateBigDecimalminPrice;privateBigDecimalmaxPrice;DateTimeFormat(patternyyyy-MM-dd HH:mm:ss)privateLocalDateTimebeginTime;DateTimeFormat(patternyyyy-MM-dd HH:mm:ss)privateLocalDateTimeendTime;}文字说明这个 DTO 封装了所有分页查询条件。这里给page和pageSize设置了默认值privateIntegerpage1;privateIntegerpageSize10;这样前端如果没传分页参数也不会直接报错。时间参数加了DateTimeFormat(patternyyyy-MM-dd HH:mm:ss)这样 Spring MVC 才能把字符串时间转换成LocalDateTime。例如前端传beginTime2026-07-01 00:00:00后端就能正常接收。五、准备商品列表 VO分页列表通常不需要返回商品详情里的全部字段所以单独定义一个列表 VO。packagecom.example.vo;importlombok.Data;importjava.math.BigDecimal;importjava.time.LocalDateTime;DatapublicclassProductPageVO{privateLongid;privateStringname;privateBigDecimalprice;privateIntegerstock;privateStringcoverUrl;privateIntegerstatus;privateLocalDateTimecreateTime;}文字说明列表页一般展示核心字段即可比如商品 ID商品名称价格库存封面状态创建时间如果列表页字段太多会导致接口响应变大也会让前端表格变得很复杂。六、Controller 层实现packagecom.example.controller;importcom.example.common.PageResult;importcom.example.common.Result;importcom.example.dto.ProductPageQueryDTO;importcom.example.service.ProductService;importcom.example.vo.ProductPageVO;importlombok.RequiredArgsConstructor;importorg.springframework.web.bind.annotation.*;RestControllerRequestMapping(/admin/product)RequiredArgsConstructorpublicclassAdminProductController{privatefinalProductServiceproductService;GetMapping(/page)publicResultPageResultProductPageVOpage(ProductPageQueryDTOqueryDTO){PageResultProductPageVOpageResultproductService.page(queryDTO);returnResult.success(pageResult);}}文字说明这里没有使用RequestBody。因为分页条件查询一般是 GET 请求参数通常放在 URL 后面例如GET /admin/product/page?page1pageSize10keyword手机status1Spring MVC 会自动把这些请求参数封装到ProductPageQueryDTO中。Controller 层依旧保持简单只负责接收参数、调用 Service、返回结果。七、Service 层实现分页逻辑1. Service 接口packagecom.example.service;importcom.example.common.PageResult;importcom.example.dto.ProductPageQueryDTO;importcom.example.vo.ProductPageVO;publicinterfaceProductService{PageResultProductPageVOpage(ProductPageQueryDTOqueryDTO);}2. Service 实现类packagecom.example.service.impl;importcom.example.common.PageResult;importcom.example.common.ResultCode;importcom.example.dto.ProductPageQueryDTO;importcom.example.exception.BusinessException;importcom.example.mapper.ProductMapper;importcom.example.service.ProductService;importcom.example.vo.ProductPageVO;importcom.github.pagehelper.Page;importcom.github.pagehelper.PageHelper;importlombok.RequiredArgsConstructor;importorg.springframework.stereotype.Service;importjava.util.List;ServiceRequiredArgsConstructorpublicclassProductServiceImplimplementsProductService{privatestaticfinalintMAX_PAGE_SIZE100;privatefinalProductMapperproductMapper;OverridepublicPageResultProductPageVOpage(ProductPageQueryDTOqueryDTO){checkPageParams(queryDTO);PageHelper.startPage(queryDTO.getPage(),queryDTO.getPageSize());ListProductPageVOlistproductMapper.selectPage(queryDTO);PageProductPageVOpage(PageProductPageVO)list;returnnewPageResult(page.getTotal(),page.getResult());}privatevoidcheckPageParams(ProductPageQueryDTOqueryDTO){if(queryDTOnull){thrownewBusinessException(ResultCode.PARAM_ERROR);}if(queryDTO.getPage()null||queryDTO.getPage()0){queryDTO.setPage(1);}if(queryDTO.getPageSize()null||queryDTO.getPageSize()0){queryDTO.setPageSize(10);}if(queryDTO.getPageSize()MAX_PAGE_SIZE){queryDTO.setPageSize(MAX_PAGE_SIZE);}if(queryDTO.getMinPrice()!nullqueryDTO.getMaxPrice()!nullqueryDTO.getMinPrice().compareTo(queryDTO.getMaxPrice())0){thrownewBusinessException(ResultCode.PARAM_ERROR.getCode(),最低价格不能大于最高价格);}if(queryDTO.getBeginTime()!nullqueryDTO.getEndTime()!nullqueryDTO.getBeginTime().isAfter(queryDTO.getEndTime())){thrownewBusinessException(ResultCode.PARAM_ERROR.getCode(),开始时间不能晚于结束时间);}}}文字说明Service 层主要做三件事。第一校验分页参数checkPageParams(queryDTO);防止前端传入异常参数比如page-1、pageSize99999。第二开启分页PageHelper.startPage(queryDTO.getPage(),queryDTO.getPageSize());注意PageHelper.startPage必须紧挨着下一次 MyBatis 查询执行。第三封装分页结果returnnewPageResult(page.getTotal(),page.getResult());这样前端拿到的就是统一分页格式。八、Mapper 层实现1. Mapper 接口packagecom.example.mapper;importcom.example.dto.ProductPageQueryDTO;importcom.example.vo.ProductPageVO;importorg.apache.ibatis.annotations.Mapper;importjava.util.List;MapperpublicinterfaceProductMapper{ListProductPageVOselectPage(ProductPageQueryDTOqueryDTO);}2. Mapper XML 动态 SQL?xml version1.0 encodingUTF-8 ?!DOCTYPEmapperPUBLIC-//mybatis.org//DTD Mapper 3.0//ENhttps://mybatis.org/dtd/mybatis-3-mapper.dtdmappernamespacecom.example.mapper.ProductMapperselectidselectPageresultTypecom.example.vo.ProductPageVOselect id, name, price, stock, cover_url, status, create_time from productwhereiftestkeyword ! null and keyword ! and name like concat(%, #{keyword}, %)/ififteststatus ! nulland status #{status}/ififtestminPrice ! nulland pricegt; #{minPrice}/ififtestmaxPrice ! nulland pricelt; #{maxPrice}/ififtestbeginTime ! nulland create_timegt; #{beginTime}/ififtestendTime ! nulland create_timelt; #{endTime}/if/whereorder by create_time desc, id desc/select/mapper文字说明这里使用了 MyBatis 动态 SQL。where标签会自动处理where和多余的and。比如只传了status时最终 SQL 类似select...fromproductwherestatus?orderbycreate_timedesc,iddesc如果什么条件都没传就不会拼接where最终查询全部商品分页。关键字查询使用name like concat(%, #{keyword}, %)价格和时间范围分别使用price #{minPrice} price #{maxPrice} create_time #{beginTime} create_time #{endTime}这就是后台列表条件查询里非常常见的写法。九、分页查询接口测试示例1. 查询第一页GET /admin/product/page?page1pageSize10表示查询第一页每页 10 条。2. 根据名称模糊查询GET /admin/product/page?page1pageSize10keyword手机表示查询名称中包含“手机”的商品。3. 根据状态查询GET /admin/product/page?page1pageSize10status1表示查询上架商品。4. 根据价格区间查询GET /admin/product/page?page1pageSize10minPrice100maxPrice500表示查询价格在 100 到 500 之间的商品。5. 根据时间范围查询GET /admin/product/page?page1pageSize10beginTime2026-07-01 00:00:00endTime2026-07-05 23:59:59表示查询指定时间范围内创建的商品。十、为什么要限制 pageSize如果不限制pageSize前端或者恶意请求可能传pageSize100000这样一次查询就可能查出大量数据导致数据库压力变大接口响应变慢内存占用升高前端渲染卡顿所以 Service 里做了限制if(queryDTO.getPageSize()MAX_PAGE_SIZE){queryDTO.setPageSize(MAX_PAGE_SIZE);}后台管理系统中一般每页 10、20、50 条已经够用最大限制到 100 比较合理。十一、涉及知识点1. PageHelperPageHelper 是 MyBatis 常用分页插件。它可以自动帮我们拼接分页 SQL并计算总数。使用时要注意PageHelper.startPage(page,pageSize);必须放在 Mapper 查询之前。2. MyBatis 动态 SQL动态 SQL 适合处理可选查询条件。常用标签有whereifsetforeach本篇主要使用where和if。3. DTO 和 VO 分层ProductPageQueryDTO用来接收查询参数。ProductPageVO用来返回列表数据。这样比直接用实体类更清晰也更适合前后端接口设计。4. 模糊查询模糊查询常见写法是namelikeconcat(%,#{keyword}, %)注意不要直接拼接字符串避免 SQL 注入风险。5. 时间范围查询时间范围查询是后台列表中非常常见的需求。前端传字符串时间后端通过DateTimeFormat转成LocalDateTimeMapper 再用于 SQL 查询。十二、常见问题1. PageHelper 为什么没有生效常见原因有没有引入 PageHelper starterPageHelper.startPage没有紧挨着 Mapper 查询Mapper 查询之前执行了其他查询返回类型转换方式不正确正确顺序是PageHelper.startPage(page,pageSize);ListProductPageVOlistproductMapper.selectPage(queryDTO);PageProductPageVOpage(PageProductPageVO)list;2. GET 请求能不能接收对象可以。只要参数名和 DTO 字段名一致Spring MVC 会自动封装。例如?page1pageSize10keyword手机可以封装到ProductPageQueryDTO3. 查询条件为空时会不会 SQL 报错不会。因为where会自动处理条件。如果所有if都不满足就不会生成where。4. 排序字段能不能让前端传可以但要谨慎。不能直接把前端传入的排序字段拼进 SQL否则有 SQL 注入风险。更安全的做法是后端定义白名单比如只允许按create_time、price、stock排序。十三、实际开发建议1. 分页参数一定要兜底不要完全相信前端传参。后端要处理页码为空页码小于 1每页条数为空每页条数过大2. 列表接口不要返回过多字段列表页只返回展示需要的字段。详情页再返回完整信息。这样接口更轻前端也更好处理。3. 模糊查询字段要考虑索引like %关键字%在数据量大时可能导致索引效果不好。如果商品量很大可以考虑只支持前缀匹配增加搜索字段使用全文索引接入 Elasticsearch4. 后台查询要注意组合条件后台列表的核心不只是分页而是多个条件组合查询。所以动态 SQL 是这一类接口必须掌握的内容。十四、总结这一篇我们完成了 Spring Boot MyBatis 分页条件查询接口。整个流程包括定义分页返回对象PageResult定义查询参数 DTO定义列表返回 VOController 接收 GET 查询参数Service 校验分页参数并调用 PageHelperMapper 使用动态 SQL 组合查询条件返回统一分页结构给前端相比基础 CRUD这一篇更接近后台管理系统真实列表页的写法。分页条件查询是非常高频的能力不管是商品、用户、订单、日志基本都能套用这一套思路。学会这篇之后后面写后台列表接口会顺很多。

相关新闻

这个 App 明天就要上线了

这个 App 明天就要上线了

「审核已通过。」林川坐在工位上,看着 App Store Connect 里那行绿色的小字。 周围是下午四点钟寻常的办公室噪音——键盘声、空调声、隔壁运营在打电话。 他截图,发到群里。 群里炸了。「川哥牛逼!」 「终于!!&#x…

2026/10/9 22:41:57 阅读更多 →
智慧水利机器视觉应用:AI视觉水位监测完整技术剖析

智慧水利机器视觉应用:AI视觉水位监测完整技术剖析

摘要在智慧水利数字化转型全面推进的行业背景下,传统接触式水位监测设备存在易淤堵、运维成本高、极端天气观测安全风险大、无实景佐证等短板,已难以满足河道、水库、灌区、城市内涝等多场景精细化水文管控需求。本文围绕AI视觉水位监测这套非接触式全自…

2026/10/11 15:59:02 阅读更多 →
PVC鞋材稳定剂应用与发展

PVC鞋材稳定剂应用与发展

PVC材质凭借柔韧耐磨、性价比突出的优势,广泛应用于各类鞋材生产加工领域,但PVC树脂在高温塑化、注塑发泡等加工工序中,容易出现分解变色、材质脆化、物料析出等问题,直接影响鞋材成品质量与使用寿命,而PVC鞋材稳定剂(…

2026/10/11 8:16:21 阅读更多 →

最新新闻

lpmake制作super镜像实践:动态分区打包参数与常见坑

lpmake制作super镜像实践:动态分区打包参数与常见坑

简介:这是一份面向Android动态分区与超级镜像场景的lpmake/lpunpack完整源码资源包,适合系统镜像定制、分区表生成、镜像解包调试等方向的工程师与进阶学习者。整包文件共1417个,压缩后大小约24.23MB;其中h、c、cpp、cc等C/C源文件…

2026/10/13 4:36:09 阅读更多 →
EVTX日志解析:从二进制结构到高效安全读取

EVTX日志解析:从二进制结构到高效安全读取

简介:面向安全取证、入侵检测与Windows日志审计场景,这份资源是基于Rust实现的EVTX快速跨平台解析器。它以100%安全代码编写,支持多线程解析,可直接将XML事件日志转换为XML或JSON输出,并具备丢失记录与损坏块的恢复能力…

2026/10/13 4:36:09 阅读更多 →
Python黑白照片上色:Lab色彩空间与U-Net模型训练实战

Python黑白照片上色:Lab色彩空间与U-Net模型训练实战

简介:面向Python开发者和图像处理爱好者,这份压缩包提供了一套基于深度学习的黑白图片上色实现方案,核心围绕卷积神经网络完成从数据准备、模型训练到色彩预测的完整流程,适合作为图像色彩化项目的入门参考。包体压缩后仅8KB&…

2026/10/13 4:36:09 阅读更多 →
混合计算模式实战:从批流分离到统一资源与数据架构

混合计算模式实战:从批流分离到统一资源与数据架构

做了几年大数据基础架构,我越来越明确一个判断:大数据领域的分布式计算,单靠任何一种计算模式,都撑不起现代数据平台的复杂度。混合计算模式,简单说就是在同一套分布式计算底座之上,同时承载离线批处理、实…

2026/10/13 4:36:09 阅读更多 →
Oracle表被锁怎么解锁?从v$lock定位到KILL SESSION的实操指南

Oracle表被锁怎么解锁?从v$lock定位到KILL SESSION的实操指南

某天早上一进办公室,手机就开始震动:某业务系统在前一天夜里开始批量任务,早上核心表直接卡死,开发同事急匆匆跑过来说“订单表挂了,select都出不来,整个后台进不去了”。这种场景在Oracle运维里太常见了—…

2026/10/13 4:36:09 阅读更多 →
基于Hough变换的答题卡识别:从倾斜校正到填涂判定

基于Hough变换的答题卡识别:从倾斜校正到填涂判定

简介:这份资源围绕“基于Hough变换的答题卡识别”展开,面向计算机视觉、图像处理与模式识别方向的学习者和开发者,适合作为课程设计、毕业设计或算法练手项目的参考方案。内容涵盖Hough变换检测答题卡选择区域、图像分割与预处理、模式识别分…

2026/10/13 4:35:09 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →