【项目扩展实战|第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/7/23 22:31:41阅读更多 →
AI数字人形象定制如何做到“一秒辨真伪”?揭秘头部平台未公开的8项微表情校准指标

AI数字人形象定制如何做到“一秒辨真伪”?揭秘头部平台未公开的8项微表情校准指标

更多请点击: https://intelliparadigm.com 第一章:AI数字人形象定制如何做到“一秒辨真伪”? 在高保真AI数字人构建中,“一秒辨真伪”并非追求绝对不可分辨,而是通过多维度感知一致性实现人类视觉与认知系统的瞬时信任…

2026/7/23 22:29:40阅读更多 →
【AI数字人直播变现实战手册】:0基础7天打造24小时自动带货直播间

【AI数字人直播变现实战手册】:0基础7天打造24小时自动带货直播间

更多请点击: https://intelliparadigm.com 第一章:AI数字人直播变现的核心逻辑与商业闭环 AI数字人直播并非简单地将真人主播替换成虚拟形象,其本质是构建以“低成本、高复用、强可控”为特征的自动化内容生产与用户价值转化系统。核心逻辑在…

2026/7/23 22:29:40阅读更多 →
AI工程化实践:Claw六步法解决企业AI落地难题

AI工程化实践:Claw六步法解决企业AI落地难题

1. 项目概述:当AI走出实验室去年参与某制造业客户的质量检测系统升级时,他们的CTO对我说:"我们采购的AI模型在测试集上准确率98%,但产线实际部署后连70%都达不到。"这个场景完美诠释了当前企业AI落地面临的困境——从PO…

2026/7/24 7:25:48阅读更多 →
YOLO11-SEG模型在钢水罐检测中的工业应用与优化

YOLO11-SEG模型在钢水罐检测中的工业应用与优化

1. 钢水罐检测的行业背景与技术挑战在钢铁冶炼行业,钢水罐(也称为钢包)是承载高温钢水进行转运和浇铸的核心设备。其安全状态直接关系到生产效率和人员安全。传统的人工检测方式存在以下痛点:高温环境限制:钢水罐表面温…

2026/7/24 7:25:48阅读更多 →
从传统开发到AI大模型:技术转型与高薪秘籍

从传统开发到AI大模型:技术转型与高薪秘籍

1. 从传统开发到算法大模型的转型之路去年这个时候,我还在用Spring Boot写着业务代码,每天和产品经理battle需求合理性。如今坐在字节的工位上调试大模型参数时,常常会想起那个在CRUD中挣扎的自己。这张月薪11万的工资条,记录的不…

2026/7/24 7:25:48阅读更多 →
MSP430F16x到F261x迁移实战:硬件改动、固件重写与避坑指南

MSP430F16x到F261x迁移实战:硬件改动、固件重写与避坑指南

1. 项目概述与迁移价值如果你手头有一个基于TI MSP430F16x系列MCU(比如经典的F169)的老项目,现在因为芯片停产、成本优化或者想提升性能,需要迁移到新一代的MSP430F261x上,那你来对地方了。这活儿我干过不止一次&#…

2026/7/24 7:25:48阅读更多 →
AIGC检测系统原理与学术论文降重实战指南

AIGC检测系统原理与学术论文降重实战指南

1. 项目背景与核心挑战去年第三季度开始,国内高校普遍引入了学术不端检测系统对毕业论文进行AIGC内容识别。作为某985高校计算机专业的研三学生,我在提交学位论文预审时遇到了棘手情况:维普系统的AIGC检测结果显示我的实验方法章节有37%的疑似…

2026/7/24 7:25:48阅读更多 →
SAR ADC评估套件实战:从硬件配置到性能分析的完整指南

SAR ADC评估套件实战:从硬件配置到性能分析的完整指南

1. 项目概述:深入解析SAR ADC评估套件的核心价值在信号链设计的核心地带,模数转换器(ADC)扮演着将现实世界连续变化的模拟信号,精准转换为数字系统可处理的离散代码的关键角色。对于追求高精度、中等速度与低功耗平衡的…

2026/7/24 7:23:47阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/24 0:58:53阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/24 0:58:53阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/24 0:58:53阅读更多 →
我的编程之路:第一篇博客

我的编程之路:第一篇博客

大家好,我是一名编程初学者,同时这也是我编程学习之路上的第一篇博客。在这里,我想要向大家介绍我的一些想法和规划。a.自我介绍我是一个刚刚接触编程的新手,目前在学习c语言,我对编程世界充满了强烈的好奇。当然&…

2026/7/24 0:00:06阅读更多 →
【LeetCode 54】螺旋矩阵

【LeetCode 54】螺旋矩阵

问题描述: 解法: 1、模拟(参考自【LeetCode 54】螺旋矩阵-CSDN博客) int *spiralOrder(int **matrix, int matrixSize, int *matrixColSize, int *returnSize) {static const int dirs[4][2] {{0, 1}, {1, 0}, {0, -1}, {-1, …

2026/7/24 0:00:06阅读更多 →
2026 WAIC:模型隐身、智能体疯野,厂商竞赛聚焦办公场景与商业闭环

2026 WAIC:模型隐身、智能体疯野,厂商竞赛聚焦办公场景与商业闭环

知春路不相信模型领先今年WAIC大会,昔日AI六小龙来了五家,分别是Kimi、阶跃星辰、Minimax、百川智能、零一万物。连放弃基模的百川和零一万物都来了,唯一缺席的竟是近几个月来风光无限的智谱。(DeepSeek一直不参加)WAI…

2026/7/24 0:00:06阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/23 22:58:43阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/23 18:58:18阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/23 18:58:18阅读更多 →