恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

【项目扩展实战|第8篇】Spring Boot + MyBatis 分页条件查询接口优化

  • 首页
  • 资讯中心
  • /
  • 【项目扩展实战|第8篇】Spring Boot + MyBatis 分页条件查询接口优化

相关资讯

这个 App 明天就要上线了 2026/8/2 18:55:33
Vision Transformer Slimming: Multi-Dimension Searching in Continuous Optimization Space 解读 2026/8/2 18:55:33
MCP 协议与 Claude Code 实战 2026/8/2 18:55:34

最新资讯

DeepSeek Harness 16款插件实测:从VS Code到浏览器自动化工具链
3D游戏图形渲染数学原理:从矩阵变换到GPU着色器实战
坐标转换与七参数解算实战:从Coord MG到RTK参数落地全流程
UML关联关系深度解析:从基础概念到面向对象设计实践
洁哥EX关爱心彩蛋怎么找?三步定位地图隐藏设计
AI Agent 也能写工程计算书?用 Codex 自动生成简支梁计算文档全流程

今日推荐

Redis缓存与离线预计算在大数据处理中的实战应用
Android 12热启动闪屏排查:从冷热启动差异到官方SplashScreen避坑指南
加密资产价值投资:原理、方法与实战策略

本周热门

超人会飞不算本事:系统稳定依赖清晰规则与边界设计
超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论
基于CNN的调制信号识别:MATLAB实现时频图分类实战

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

【项目扩展实战|第8篇】Spring Boot + MyBatis 分页条件查询接口优化

发布时间:2026/9/8 5:20:16
【项目扩展实战|第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这一篇更接近后台管理系统真实列表页的写法。分页条件查询是非常高频的能力不管是商品、用户、订单、日志基本都能套用这一套思路。学会这篇之后后面写后台列表接口会顺很多。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号