当前位置: 首页 > java >正文

后端分页接口实现

分页查询学习文档

文档概述

创建时间: 2025-07-24
文档版本: v1.0
适用范围: Spring Boot + MyBatis + PageHelper 分页查询实现
技术栈: Spring Boot 2.7.18 + PageHelper + PageObject 继承模式


1. 分页查询架构设计

1.1 整体架构

Controller 层 (接收分页参数)↓
Service 层 (业务逻辑处理)↓
Mapper 层 (数据库查询)↓
PageHelper (分页插件)↓
数据库 (执行分页SQL)

1.2 核心组件

  • PageObject: 分页基类,包含分页和排序字段
  • PageHelper: MyBatis分页插件
  • PageInfo: 分页结果包装类
  • ResponseResult: 统一响应格式
  • PageResult: 分页响应数据结构

2. PageObject 基类设计

2.1 PageObject 类结构

package com.hn.shengdun.base;import java.io.Serializable;/*** 分页查询基类* 所有分页请求类都应该继承此类*/
public class PageObject implements Serializable {/*** 页码(从1开始)*/private Integer pageNum;/*** 页大小*/private Integer pageSize;/*** 排序字段*/private String orderByColumn;/*** 排序方向(asc/desc)*/private String isAsc;// getter/setter 方法public Integer getPageNum() {return pageNum;}public void setPageNum(Integer pageNum) {this.pageNum = pageNum;}public Integer getPageSize() {return pageSize;}public void setPageSize(Integer pageSize) {this.pageSize = pageSize;}public String getOrderByColumn() {return orderByColumn;}public void setOrderByColumn(String orderByColumn) {this.orderByColumn = orderByColumn;}public String getIsAsc() {return isAsc;}public void setIsAsc(String isAsc) {this.isAsc = isAsc;}
}

2.2 字段说明

字段名类型说明默认值示例
pageNumInteger页码(从1开始)11, 2, 3
pageSizeInteger每页记录数1010, 20, 50
orderByColumnString排序字段名null“create_time”, “dict_id”
isAscString排序方向“desc”“asc”, “desc”

3. 分页请求类设计

3.1 设计原则

  1. 继承 PageObject: 获得统一的分页字段
  2. 实现 Serializable: 支持序列化
  3. 包含业务字段: 支持条件查询
  4. 设置默认值: 提供合理的默认分页参数

3.2 字典数据分页请求类示例

package com.hn.bitao.defined.report.module.dto;import com.hn.shengdun.base.PageObject;
import java.io.Serializable;/*** 字典数据分页查询请求DTO* * @author 系统生成*/
public class DictDataPageRequest extends PageObject implements Serializable {/*** 字典编码(精确查询)*/private Long dictCode;/*** 字典标签(模糊查询)*/private String dictLabel;/*** 字典键值(模糊查询)*/private String dictValue;/*** 字典类型(模糊查询)*/private String dictType;/*** 状态(0正常 1停用)*/private String status;/*** 创建者(模糊查询)*/private String createBy;/*** 开始时间(创建时间范围查询)*/private String beginTime;/*** 结束时间(创建时间范围查询)*/private String endTime;// 构造函数public DictDataPageRequest() {super();// 设置默认值this.setPageNum(1);this.setPageSize(10);this.setIsAsc("desc");}// Getter and Setter methodspublic Long getDictCode() {return dictCode;}public void setDictCode(Long dictCode) {this.dictCode = dictCode;}public String getDictLabel() {return dictLabel;}public void setDictLabel(String dictLabel) {this.dictLabel = dictLabel;}public String getDictValue() {return dictValue;}public void setDictValue(String dictValue) {this.dictValue = dictValue;}public String getDictType() {return dictType;}public void setDictType(String dictType) {this.dictType = dictType;}public String getStatus() {return status;}public void setStatus(String status) {this.status = status;}public String getCreateBy() {return createBy;}public void setCreateBy(String createBy) {this.createBy = createBy;}public String getBeginTime() {return beginTime;}public void setBeginTime(String beginTime) {this.beginTime = beginTime;}public String getEndTime() {return endTime;}public void setEndTime(String endTime) {this.endTime = endTime;}@Overridepublic String toString() {return "DictDataPageRequest{" +"dictCode=" + dictCode +", dictLabel='" + dictLabel + '\'' +", dictValue='" + dictValue + '\'' +", dictType='" + dictType + '\'' +", status='" + status + '\'' +", createBy='" + createBy + '\'' +", beginTime='" + beginTime + '\'' +", endTime='" + endTime + '\'' +", pageNum=" + getPageNum() +", pageSize=" + getPageSize() +", orderByColumn='" + getOrderByColumn() + '\'' +", isAsc='" + getIsAsc() + '\'' +'}';}
}

3.3 设计要点

  1. 继承关系: extends PageObject implements Serializable
  2. 默认值设置: 在构造函数中设置合理的默认分页参数
  3. 字段注释: 明确标注每个字段的查询类型(精确/模糊/范围)
  4. toString方法: 包含所有字段,便于调试

4. Controller 层实现

4.1 分页查询接口实现

package com.hn.bitao.defined.report.module.web.controller;import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.hn.shengdun.base.PageResult;
import com.hn.shengdun.base.ResponseResult;
import com.hn.bitao.defined.report.module.pojo.DictData;
import com.hn.bitao.defined.report.module.dto.DictDataPageRequest;
import com.hn.bitao.defined.report.module.service.DictDataService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import javax.validation.Valid;
import java.util.List;@RestController
@RequestMapping("/dict/data")
public class DictDataController {private static final Logger logger = LoggerFactory.getLogger(DictDataController.class);@Autowiredprivate DictDataService dictDataService;/*** 分页查询字典数据列表*/@PostMapping("/page")public ResponseResult<PageResult<DictData>> pageDictData(@Valid @RequestBody DictDataPageRequest pageRequest) {logger.info("分页查询字典数据列表,页码: {}, 页大小: {}", pageRequest.getPageNum(), pageRequest.getPageSize());// 将DTO转换为实体对象,只复制实体中存在的字段DictData dictData = new DictData();dictData.setDictLabel(pageRequest.getDictLabel());dictData.setDictValue(pageRequest.getDictValue());dictData.setDictType(pageRequest.getDictType());dictData.setStatus(pageRequest.getStatus());dictData.setCreateBy(pageRequest.getCreateBy());// beginTime和endTime是查询参数,不复制到实体对象中// 处理排序if (pageRequest.getOrderByColumn() != null && !pageRequest.getOrderByColumn().isEmpty()) {String orderBy = pageRequest.getOrderByColumn();if ("desc".equals(pageRequest.getIsAsc())) {orderBy += " desc";} else {orderBy += " asc";}PageHelper.orderBy(orderBy);}// 启动分页PageHelper.startPage(pageRequest.getPageNum(), pageRequest.getPageSize(), true);// 执行查询List<DictData> list = dictDataService.selectDictDataList(dictData);// 获取分页信息PageInfo<DictData> pageInfo = new PageInfo<>(list);// 返回分页结果return ResponseResult.successPage(list, pageInfo.getTotal());}
}

4.2 实现步骤详解

步骤1:参数接收和验证
@PostMapping("/page")
public ResponseResult<PageResult<DictData>> pageDictData(@Valid @RequestBody DictDataPageRequest pageRequest) {// @Valid 注解启用参数校验// @RequestBody 接收JSON格式的请求体
}
步骤2:DTO转实体对象
// 将DTO转换为实体对象,只复制实体中存在的字段
DictData dictData = new DictData();
dictData.setDictLabel(pageRequest.getDictLabel());
dictData.setDictValue(pageRequest.getDictValue());
dictData.setDictType(pageRequest.getDictType());
dictData.setStatus(pageRequest.getStatus());
dictData.setCreateBy(pageRequest.getCreateBy());
// beginTime和endTime是查询参数,不复制到实体对象中
步骤3:处理排序
// 处理排序
if (pageRequest.getOrderByColumn() != null && !pageRequest.getOrderByColumn().isEmpty()) {String orderBy = pageRequest.getOrderByColumn();if ("desc".equals(pageRequest.getIsAsc())) {orderBy += " desc";} else {orderBy += " asc";}PageHelper.orderBy(orderBy);
}
步骤4:启动分页
// 启动分页
PageHelper.startPage(pageRequest.getPageNum(), pageRequest.getPageSize(), true);
// 参数说明:
// pageRequest.getPageNum() - 页码
// pageRequest.getPageSize() - 页大小
// true - 是否进行count查询(获取总记录数)
步骤5:执行查询
// 执行查询
List<DictData> list = dictDataService.selectDictDataList(dictData);
// 注意:PageHelper.startPage() 之后的第一个查询会被自动分页
步骤6:获取分页信息
// 获取分页信息
PageInfo<DictData> pageInfo = new PageInfo<>(list);
// PageInfo 包含了分页的所有信息:总记录数、总页数、当前页等
步骤7:返回结果
// 返回分页结果
return ResponseResult.successPage(list, pageInfo.getTotal());
// ResponseResult.successPage() 是项目封装的分页响应方法

5. Service 层实现

5.1 Service 接口定义

package com.hn.bitao.defined.report.module.service;import com.hn.bitao.defined.report.module.pojo.DictData;
import java.util.List;/*** 字典数据服务接口*/
public interface DictDataService {/*** 查询字典数据列表* @param dictData 查询条件* @return 字典数据列表*/List<DictData> selectDictDataList(DictData dictData);
}

5.2 Service 实现类

package com.hn.bitao.defined.report.module.service.impl;import com.hn.bitao.defined.report.module.mapper.DictDataMapper;
import com.hn.bitao.defined.report.module.pojo.DictData;
import com.hn.bitao.defined.report.module.service.DictDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class DictDataServiceImpl implements DictDataService {@Autowiredprivate DictDataMapper dictDataMapper;@Overridepublic List<DictData> selectDictDataList(DictData dictData) {return dictDataMapper.selectDictDataList(dictData);}
}

5.3 Service 层要点

  1. 简单转发: Service层通常只是简单地调用Mapper层方法
  2. 业务逻辑: 如果有复杂的业务逻辑,在Service层处理
  3. 事务管理: 可以在Service层添加@Transactional注解

6. Mapper 层实现

6.1 Mapper 接口定义

package com.hn.bitao.defined.report.module.mapper;import com.hn.bitao.defined.report.module.pojo.DictData;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;/*** 字典数据Mapper接口*/
@Mapper
public interface DictDataMapper {/*** 查询字典数据列表* @param dictData 查询条件* @return 字典数据列表*/List<DictData> selectDictDataList(DictData dictData);
}

6.2 MyBatis XML 映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hn.bitao.defined.report.module.mapper.DictDataMapper"><!-- 字典数据结果映射 --><resultMap id="DictDataResult" type="DictData"><id     property="dictCode"   column="dict_code"   /><result property="dictSort"   column="dict_sort"   /><result property="dictLabel"  column="dict_label"  /><result property="dictValue"  column="dict_value"  /><result property="dictType"   column="dict_type"   /><result property="cssClass"   column="css_class"   /><result property="listClass"  column="list_class"  /><result property="isDefault"  column="is_default"  /><result property="status"     column="status"      /><result property="createBy"   column="create_by"   /><result property="createTime" column="create_time" /><result property="updateBy"   column="update_by"   /><result property="updateTime" column="update_time" /><result property="remark"     column="remark"      /></resultMap><!-- 查询字典数据列表 --><select id="selectDictDataList" parameterType="DictData" resultMap="DictDataResult">SELECT dict_code, dict_sort, dict_label, dict_value, dict_type,css_class, list_class, is_default, status,create_by, create_time, update_by, update_time, remarkFROM sys_dict_data<where><if test="dictType != null and dictType != ''">AND dict_type LIKE CONCAT('%', #{dictType}, '%')</if><if test="dictLabel != null and dictLabel != ''">AND dict_label LIKE CONCAT('%', #{dictLabel}, '%')</if><if test="dictValue != null and dictValue != ''">AND dict_value LIKE CONCAT('%', #{dictValue}, '%')</if><if test="status != null and status != ''">AND status = #{status}</if><if test="createBy != null and createBy != ''">AND create_by LIKE CONCAT('%', #{createBy}, '%')</if></where>ORDER BY dict_sort ASC</select></mapper>

6.3 SQL 编写要点

  1. 动态SQL: 使用<if>标签实现条件查询
  2. 模糊查询: 使用LIKE CONCAT('%', #{param}, '%')
  3. 精确查询: 直接使用= #{param}
  4. 默认排序: 提供默认的排序规则
  5. 字段映射: 使用resultMap映射数据库字段到Java对象

7. 分页查询测试

7.1 基础分页查询

{"pageNum": 1,"pageSize": 10,"orderByColumn": "dict_sort","isAsc": "asc"
}

PowerShell测试命令:

$body = @{pageNum = 1pageSize = 10orderByColumn = "dict_sort"isAsc = "asc"
} | ConvertTo-JsonInvoke-RestMethod -Uri "http://localhost:8080/defined-report/dict/data/page" `-Method POST `-Body $body `-ContentType "application/json"

7.2 条件分页查询

{"pageNum": 1,"pageSize": 5,"dictType": "sys_user_sex","status": "0","orderByColumn": "create_time","isAsc": "desc"
}

PowerShell测试命令:

$body = @{pageNum = 1pageSize = 5dictType = "sys_user_sex"status = "0"orderByColumn = "create_time"isAsc = "desc"
} | ConvertTo-JsonInvoke-RestMethod -Uri "http://localhost:8080/defined-report/dict/data/page" `-Method POST `-Body $body `-ContentType "application/json"

7.3 预期响应格式

{"traceId": "C52FAAF7C3B64B8E9264AF7DE749DA6C","result": true,"code": 200,"msg": "","data": {"data": [{"dictCode": 1,"dictLabel": "男","dictValue": "0","dictType": "sys_user_sex","dictSort": 1,"status": "0","createTime": "2025-01-27 10:00:00"}],"count": 10},"success": true
}

8. 常见问题和解决方案

8.1 PageHelper 不生效

问题: 分页查询返回了所有数据,没有分页效果

原因:

  1. PageHelper.startPage() 没有紧跟查询语句
  2. PageHelper.startPage() 和查询之间有其他数据库操作
  3. PageHelper 配置不正确

解决方案:

// ✅ 正确写法
PageHelper.startPage(pageNum, pageSize);
List<DictData> list = dictDataService.selectDictDataList(dictData);// ❌ 错误写法
PageHelper.startPage(pageNum, pageSize);
// 中间有其他操作
int count = dictDataService.countData();
List<DictData> list = dictDataService.selectDictDataList(dictData);

8.2 排序不生效

问题: 设置了orderByColumn但排序不生效

原因:

  1. 排序字段名不正确
  2. PageHelper.orderBy() 调用位置不对
  3. SQL中有默认排序覆盖了PageHelper的排序

解决方案:

// ✅ 正确写法
if (pageRequest.getOrderByColumn() != null && !pageRequest.getOrderByColumn().isEmpty()) {String orderBy = pageRequest.getOrderByColumn();if ("desc".equals(pageRequest.getIsAsc())) {orderBy += " desc";} else {orderBy += " asc";}PageHelper.orderBy(orderBy);
}
PageHelper.startPage(pageRequest.getPageNum(), pageRequest.getPageSize(), true);

8.3 总记录数不准确

问题: 分页查询的总记录数count不正确

原因:

  1. PageHelper.startPage() 的第三个参数设置为false
  2. 查询条件在count查询时没有生效

解决方案:

// ✅ 正确写法 - 启用count查询
PageHelper.startPage(pageRequest.getPageNum(), pageRequest.getPageSize(), true);// 确保查询条件在Mapper的XML中正确处理

8.4 内存溢出

问题: 大数据量分页查询导致内存溢出

原因:

  1. pageSize设置过大
  2. 没有合理的分页限制

解决方案:

// 在Controller中添加分页参数校验
if (pageRequest.getPageSize() > 100) {pageRequest.setPageSize(100);
}
if (pageRequest.getPageNum() < 1) {pageRequest.setPageNum(1);
}

9. 最佳实践

9.1 分页参数校验

/*** 分页参数校验和默认值设置*/
public void validatePageParams(DictDataPageRequest pageRequest) {// 页码校验if (pageRequest.getPageNum() == null || pageRequest.getPageNum() < 1) {pageRequest.setPageNum(1);}// 页大小校验if (pageRequest.getPageSize() == null || pageRequest.getPageSize() < 1) {pageRequest.setPageSize(10);}// 页大小上限if (pageRequest.getPageSize() > 100) {pageRequest.setPageSize(100);}// 排序方向校验if (pageRequest.getIsAsc() != null && !"asc".equals(pageRequest.getIsAsc()) && !"desc".equals(pageRequest.getIsAsc())) {pageRequest.setIsAsc("desc");}
}

9.2 SQL注入防护

/*** 排序字段白名单校验*/
private static final Set<String> ALLOWED_ORDER_COLUMNS = Set.of("dict_code", "dict_sort", "dict_label", "dict_value", "dict_type", "status", "create_time", "update_time"
);public void validateOrderColumn(String orderByColumn) {if (orderByColumn != null && !ALLOWED_ORDER_COLUMNS.contains(orderByColumn)) {throw new IllegalArgumentException("不支持的排序字段: " + orderByColumn);}
}

9.3 性能优化

/*** 大数据量分页优化*/
@PostMapping("/page")
public ResponseResult<PageResult<DictData>> pageDictData(@Valid @RequestBody DictDataPageRequest pageRequest) {// 1. 参数校验validatePageParams(pageRequest);validateOrderColumn(pageRequest.getOrderByColumn());// 2. 缓存热点数据String cacheKey = generateCacheKey(pageRequest);PageResult<DictData> cachedResult = redisTemplate.opsForValue().get(cacheKey);if (cachedResult != null) {return ResponseResult.success(cachedResult);}// 3. 执行查询// ... 分页查询逻辑// 4. 缓存结果redisTemplate.opsForValue().set(cacheKey, result, Duration.ofMinutes(5));return ResponseResult.success(result);
}

9.4 日志记录

@PostMapping("/page")
public ResponseResult<PageResult<DictData>> pageDictData(@Valid @RequestBody DictDataPageRequest pageRequest) {long startTime = System.currentTimeMillis();logger.info("开始分页查询字典数据,参数: {}", pageRequest);try {// 分页查询逻辑PageResult<DictData> result = performPageQuery(pageRequest);long endTime = System.currentTimeMillis();logger.info("分页查询完成,耗时: {}ms, 返回记录数: {}, 总记录数: {}", endTime - startTime, result.getData().size(), result.getCount());return ResponseResult.success(result);} catch (Exception e) {long endTime = System.currentTimeMillis();logger.error("分页查询失败,耗时: {}ms, 参数: {}", endTime - startTime, pageRequest, e);throw e;}
}

10. 总结

10.1 分页查询核心要点

  1. 继承PageObject: 统一分页字段标准
  2. PageHelper使用: 正确的调用顺序和参数
  3. 参数校验: 防止非法参数和SQL注入
  4. 性能优化: 合理的分页大小和缓存策略
  5. 错误处理: 完善的异常处理和日志记录

10.2 开发流程

  1. 设计分页请求类: 继承PageObject,添加业务字段
  2. 实现Controller: 参数接收、排序处理、分页启动
  3. 实现Service: 业务逻辑处理(通常简单转发)
  4. 实现Mapper: 动态SQL查询,支持条件筛选
  5. 编写测试: 覆盖各种分页场景
  6. 性能调优: 监控查询性能,优化慢查询

10.3 注意事项

  1. PageHelper顺序: startPage()必须紧跟查询语句
  2. 排序安全: 使用白名单验证排序字段
  3. 分页限制: 设置合理的pageSize上限
  4. 缓存策略: 对热点数据进行缓存
  5. 监控告警: 监控分页查询的性能指标

文档维护人员: AI助手
最后更新时间: 2025年1月27日
文档状态: 完整版,包含完整的分页查询实现指南

http://www.xdnf.cn/news/16210.html

相关文章:

  • SpringBoot框架简介
  • PHP 与 Vue.js 结合的前后端分离架构
  • Qwen3-Coder实现中国象棋游戏的尝试
  • DRF - 博客列表API
  • 【C++】类和对象(中)
  • Eureka-服务注册,服务发现
  • 办公自动化入门:如何高效将图片整合为PDF文档
  • PHP文件下载
  • Lua(字符串)
  • 图论:搜索问题
  • linus 环境 tomcat启动日志分隔
  • LeetCode31~50题解
  • LeetCodeOJ题:回文链表
  • CAN总线仲裁中的延时补偿机制
  • Lua(文件I/O)
  • 【XGBoost】两个单任务的模型 MAP - Charting Student Math Misunderstandings
  • 游戏开发Unity/ ShaderLab学习路径
  • 光伏电站巡检清扫飞行机器人设计cad【6张】三维图+设计说明书
  • Java 中 Future 与 Callable 的使用详解
  • 3D Semantic Occupancy Prediction
  • Django 科普介绍:从入门到了解其核心魅力
  • 【Newman+Jenkins】实施接口自动化测试
  • 时间日期选择器组件进行日期和时间的禁用处理逻辑
  • IntelliJ IDEA中管理多版本Git子模块的完整指南
  • useContext
  • 前端学习日记(十二)
  • 三级知识点汇总(详解)【c++】——7
  • Java并发编程第八篇(CountDownLatch组件分析)
  • 基础入门 [CMD] Windows SSH 连接服务器教程(系统自带方式)
  • FreeRTOS—计数型信号量