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

【MyBatis插件】PageHelper 分页

image

前言

  在开发 Web 应用时,我们经常需要处理海量数据的展示问题。例如,在一个电商平台上,商品列表可能有成千上万条数据。如果我们一次性将所有数据返回给前端,不仅会导致页面加载缓慢,还会对数据库造成巨大压力。为了解决这个问题,分页查询应运而生。所谓分页查询,就是当查询结果数据较多时,采用按页显示的方法将数据分成若干个“页面”,每次只加载当前页面的数据,而不是一次性全部显示。通过分页可以有效减少单次查询的返回数据量,提升性能和用户体验。PageHelper 是国内非常优秀的一款开源的 MyBatis 分页插件,能够帮助我们快速实现分页功能。而且它支持基本主流与常用的数据库, 例如mysql、 oracle、mariaDB、 DB2、 SQLite、Hsqldb等,今天就给大家聊聊 PageHelper 这款分页插件。

一、PageHelper 概述

1.1 什么是 PageHelper

  在开发过程中实现分页查询,我们可以使用 SQL 语句中添加 limit 关键字的方法实现分页查询。但是查询分页内容时,需要计算相关的分页信息和参数。而且无论什么业务其分页逻辑都是类似的,所以有框架帮助我们高效实现分页功能。PageHelper 是一个基于 MyBatis 的分页插件,它通过拦截 MyBatis 的执行器,在 SQL 语句执行前后动态添加分页逻辑来实现分页功能。通过简单的配置和调用,PageHelper 可以自动处理 SQL 查询中的分页逻辑,极大地简化了分页功能的开发过程,支持多种分页方式和结果集排序、筛选等操作。
image

  • PageHelper 官网:https://pagehelper.github.io
  • PageHelper 源码:https://gitee.com/free/Mybatis_PageHelper/
  • PageHelper API:https://apidoc.gitee.com/free/Mybatis_PageHelper/

1.2 PageHelper 的工作原理

  PageHelper 的工作原理主要依赖于拦截 MyBatis 的查询操作,在查询前设置分页参数,并在执行 SQL 语句时动态添加分页逻辑,从而实现分页查询。它通过修改当前执行的 SQL 语句来添加分页条件,执行添加了分页条件的 SQL 语句,最终返回分页后的结果集。此外,PageHelper 还提供了详细的配置选项和默认参数支持,如pagehelper.reasonable、pagehelper.defaultCount等,用户可以根据自己的需求进行配置。在整合PageHelper到项目中时,需要确保已经正确导入了MyBatis的依赖,并且按照官方文档的指引进行依赖的引入和配置。

  总的来说,PageHelper 是一款功能强大且易于使用的 MyBatis 分页插件,它大大简化了分页查询的实现过程,提高了开发效率,是 Java 项目中实现分页功能的常用工具之一。

二、PageHelper 的基本使用

2.1 引入依赖

  在使用 PageHelper 之前,需要在项目中引入 PageHelper 的相关依赖。如果使用的是 Maven,可以在 pom.xml 中添加以下依赖:

<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>x.y.z</version>
</dependency>

  若是 Spring Boot中集成 PageHelper,需要在 pom.xml 中添加以下依赖即可,无需额外配置,PageHelper 会自动集成 MyBatis。

<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>x.y.z</version>
</dependency><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>x.y.z</version>
</dependency>

2.2 配置拦截器插件

在 MyBatis 配置文件中配置

  在 MyBatis 的配置文件中(通常是 mybatis-config.xml ),添加 PageHelper 的插件配置,关键代码如下所示:

<plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"> <!-- 配置全局的分页参数 --><property name="helperDialect" value="mysql"/><property name="offsetAsPageNum" value="true"/><property name="rowBoundsWithCount" value="true"/></plugin>
</plugins>

在 Spring Boot中配置

 Spring Boot 引入 starter 后自动生效,对分页插件进行配置时,通常可以通过配置文件 application.propertiesapplication.yaml 中进行配置,关键代码如下所示:

pagehelper:helper-dialect: mysql # 配置分页插件的方言,即使用的什么数据库则就用什么数据库reasonable: true 	# 开启合理查询:即若超过最大页跳到最后一页,若查询-1页,默认查询第一页。support-methods-arguments: true	# 通过 Mapper 接口方法的参数来传递分页参数params: count=countSql # 指定count查询的参数名称。

  或者可以通过配置类定义设置相关的参数信息,关键代码如下所示:

import com.github.pagehelper.PageHelper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;@Configuration
public class MyBatisConfig {@Beanpublic PageHelper pageHelper() {PageHelper pageHelper = new PageHelper();Properties properties = new Properties();properties.setProperty("dialect", "Mysql");properties.setProperty("offsetAsPageNum", "true");properties.setProperty("rowBoundsWithCount", "true");pageHelper.setProperties(properties);return pageHelper;}
}

2.3 编写 Mapper 接口和 XML 文件

为了实现 PageHelper,定义一个实体类,关键代码如下所示:

@Data
public class SysUser {private Long id;private String userName;private String password;private String phone;private String realName;private String nickName;private String email;// 账户状态(1.正常 2.锁定 )private Integer status;// 性别(1.男 2.女)private Integer sex;// 是否删除(1未删除;0已删除)private Integer deleted;private Long createUserId;private Long updateUserId;private Date createTime;private Date updateTime;
}

  编写相关 Mapper 接口和对应的 XML 文件,这些文件应该包含需要进行分页查询的 SQL 语句。注意,这里不需要特别修改 SQL 语句以支持分页,因为 PageHelper 会自动处理。假设我们有一个 UserMapper 接口,用于查询用户数据,关键代码如下所示:

@Mapper
public interface SysUserMapper {List<SysUser> selectUserList(Page<SysUser> page);
}
<?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.dllwh.mybatis.mapper.SysUserMapper"><select id="selectUserList" resultType="com.dllwh.mybatis.model.SysUser">SELECT * FROM sys_user</select>
</mapper>

2.4 实现分页查询

  在 Service 层或 Controller 层中,我们可以通过 PageHelper 提供的方法来进行分页参数的设置和获取分页结果。PageHelper 的核心方法是 PageHelper.startPage(),它的作用是为当前线程开启分页上下文,并在接下来的查询中拦截 SQL,添加分页参数。关键代码如下所示:

public interface UserService {PageInfo<User> selectUserList(int pageNum, int pageSize);
}@Service 
public class UserServiceImpl implements UserService{@Resource private SysUserMapper sysUserMapper;public PageInfo<User> selectUserList(int pageNum, int pageSize) {// 这句代码要放在查询 mapper 语句的前面,用于对数据库查询 SQL 设置分页参数PageHelper.startPage(pageNum,  pageSize);// 执行查询操作List<User> userList = sysUserMapper.selectUserList(null);// 将查询到的结果对象封装到pageInfo中,可以查看分页的各种数据return new PageInfo<>(userList);}
}

  PageHelper 利用 MyBatis 的插件机制拦截查询语句,在查询 SQL 中自动加入分页语法,如 MySQL 的 LIMIT 或 Oracle 的 ROWNUM,并执行两次 SQL 查询:

  1. 查询总记录数:执行 SELECT COUNT(*) FROM ... 获取满足条件的记录总数。
  2. 查询分页数据:在原始查询 SQL 后追加分页条件。

2.5 返回分页结果

  在 Controller 层中,我们可以将分页结果返回给前端,关键代码如下所示:

@RestController 
@RequestMapping("/user")
public class UserController {@Resource private UserService userService;@GetMapping("/list")public PageInfo<User> getUsersList(int pageNum, int pageSize) {return userService.getUsersList(pageNum,  pageSize);}
}

  从服务层获取到分页数据后,就可以在前端页面上进行展示,并添加分页导航条等控件来控制分页。

三、PageHelper 的核心功能

分页对象 Page

  Page 是一个接口,它包含分页数据以及一些基本的分页信息(如总记录数、当前页等)。当使用 PageHelper 进行分页查询时,查询结果会被自动封装到一个实现了 Page 接口的对象中。

public class Page<E> extends ArrayList<E> implements Closeable {// 页码,从1开始private int pageNum;// 页面大小private int pageSize;// 起始行private long startRow;// 末行private long endRow;// 总数private long total;// 总页数private int pages;// 包含count查询private boolean count = true;// 分页合理化private Boolean reasonable;// 当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果private Boolean pageSizeZero;// 进行count查询的列名private String countColumn;// 排序private String orderBy;// 只增加排序private boolean orderByOnly;// sql拦截处理private BoundSqlInterceptor boundSqlInterceptor;// 分页实现类,可以使用 {@link com.github.pagehelper.page.PageAutoDialect} 类中注册的别名,例如 "mysql", "oracle"private String dialectClass;// 转换count查询时保留查询的 order by 排序private Boolean keepOrderBy;// 转换count查询时保留子查询的 order by 排序private Boolean keepSubSelectOrderBy;// 异步count查询private Boolean asyncCount;
}
方法默认值简要说明
List getResult()emptyList获取分页后的数据列表
Long getTotal()0获取列表总记录数
int getPageNum()1获取当前页码
int getPageSize()10每页显示条数,默认 10
boolean isLastPage()判断是否为第一页
boolean isLastPage()判断是否为最后一页
boolean hasPreviousPage()判断是否有上一页
boolean hasNextPage()判断是否有下一页
int getPrePage()获取上一页的页码。
int getNextPage()获取下一页的页码。

分页信息封装类 PageInfo

  我们在使用 MyBatis 分页时,不论是使用自动分页还是手动分页都会使用 PageInfo 对象作为承接介质。PageInfo 是 PageHelper 提供的用于封装分页结果的对象,包含完整的分页信息。它可以将分页查询结果和分页参数封装在一个对象中,便于传输和使用。

public class PageInfo<T> extends PageSerializable<T> {// 当前页private int pageNum;// 每页的数量private int pageSize;// 当前页的数量private int size;// 当前页面第一个元素在数据库中的行号(不常用)private long startRow;// 当前页面最后一个元素在数据库中的行号(不常用)private long endRow;// 总页数private int pages;// 前一页private int prePage;// 下一页private int nextPage;// 是否为第一页private boolean isFirstPage = false;// 是否为最后一页private boolean isLastPage = false;// 是否有前一页private boolean hasPreviousPage = false;// 是否有下一页private boolean hasNextPage = false;// 导航页码数private int navigatePages;// 所有导航页号private int[] navigatepageNums;// 导航条上的第一页private int navigateFirstPage;// 导航条上的最后一页private int navigateLastPage;

  PageHelper 提供了丰富的存储和管理分页相关的参数配置选项,例如:

public PageInfo<User> getUsers(int pageNum, int pageSize, String orderBy) {PageHelper.startPage(pageNum,  pageSize).setOrderBy(orderBy);List<User> userList = userMapper.selectUsers(null); return new PageInfo<>(userList);
}

  PageInfo 作为后端返回给前端的标准分页数据格式,便于前端渲染分页组件。它不仅包含了分页数据,还提供了更多的辅助信息,如是否为第一页、最后一页、导航页码等,前端在使用时无需手动计算分页参数,提高可读性和可维护性。

public void print PageInfo pageInfo) {System.out.println(" 当前页码:" + pageInfo.getPageNum()); System.out.println(" 总记录数:" + pageInfo.getTotal()); System.out.println(" 总页数:" + pageInfo.getPages()); 
}

四、PageHelper 实现原理

4.1 使用 ThreadLocal 记录分页参数

在调用 startPage 方法时,会通过 ThreadLocal 存储当前分页参数:

public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {Page<E> page = new Page<E>(pageNum, pageSize, count);page.setReasonable(reasonable);page.setPageSizeZero(pageSizeZero);//当已经执行过orderBy的时候Page<E> oldPage = getLocalPage();if (oldPage != null && oldPage.isOrderByOnly()) {page.setOrderBy(oldPage.getOrderBy());}//设置ThreadLocalsetLocalPage(page);return page;
}

附录

分页插件参数

分页插件提供了多个可选参数,这些参数使用时,按照上面配置方式中的示例配置即可。分页插件可选参数如下表所示:

属性简要说明
Boolean offsetAsPageNum通常用于指定是否将传入的 offset 当作 pageNum 页码使用。
因为 PageHelper 默认使用页码(pageNum)、每页记录数(pageSize)来进行分页的。
Boolean rowBoundsWithCount用于指定是否进行 count 查询以获取总记录数。
设置为 true 表示 PageHelper 在执行分页查询时,会先执行一个 count 查询来获取总记录数。
Boolean pageSizeZero
Boolean reasonable
Boolean supportMethodsArguments
String dialect不同数据库 SQL 语句不同,指定了数据库方言为 Mysql。
String helperDialect指定分页插件使用哪种方言,候选值可参考 PageAutoDialect.java。
Boolean autoRuntimeDialect设置为 true 时,允许在运行时根据多数据源自动识别对应方言
Boolean autoDialect
Boolean closeConn通过该属性来设置是否关闭获取的这个连接,默认 true 关闭,设置为 false 后,不会关闭获取的连接。
String params
Boolean defaultCount用于控制默认不带 count 查询的方法中,是否执行 count 查询,默认 true 会执行 count 查询。
String dialectAlias允许配置自定义实现的 别名,可以用于根据 JDBC URL 自动获取对应实现,允许通过此种方式覆盖已有的实现,
String autoDialectClass用于自动获取数据库类型
String countColumn用于配置自动 count 查询时的查询列,默认值 0,也就是 count(0)。
String replaceSql可选值为 regex 和 simple,默认值空时采用 regex 方式
String sqlCacheClass针对 SQLServer 生成的 count 和 page sql 进行缓存
String boundSqlInterceptors
Boolean keepOrderBy转换count查询时保留查询的 order by 排序。
Boolean keepSubSelectOrderBy转换count查询时保留子查询的 order by 排序。
String sqlParser
Boolean asyncCount
String countSqlParser
String orderBySqlParser
String sqlServerSqlParser

常见问题及解决方法

分页上下文未清理导致干扰

  在同一个查询操作中,如果多次调用 PageHelper.startPage() 方法,可能会导致分页参数被覆盖或产生不可预期的结果。

/*** 第一次分页查询*/
PageHelper.startPage(1, 10);
List<DataA> listA = mapperA.queryDataA();/*** 第二次分页查询*/
PageHelper.startPage(2, 5);
List<DataB> listB = mapperB.queryDataB();// 结果可能被第二次分页干扰
PageInfo<DataA> pageInfoA = new PageInfo<>(listA);

  对此,在每次分页查询后,调用 PageHelper.clearPage() 清理上下文。

PageHelper.startPage(1, 10);
List<DataA> listA = mapperA.queryDataA();
PageInfo<DataA> pageInfoA = new PageInfo<>(listA);
// 清理上下文
PageHelper.clearPage();PageHelper.startPage(2, 5);
List<DataB> listB = mapperB.queryDataB();
PageInfo<DataB> pageInfoB = new PageInfo<>(listB);
PageHelper.clearPage();

查询未执行导致上下文污染

  如果分页查询代码在条件分支中,而分支未被执行,分页上下文未被清理会干扰后续查询。

PageHelper.startPage(1, 10);
if (someCondition) {List<Data> list = mapper.queryData(); // 查询未执行
}
// 后续查询会被干扰

  对此,可以将分页查询代码移到条件分支内部,确保分页逻辑与查询一一对应。或者在条件分支中调用 PageHelper.clearPage() 清理上下文。

总结

  通过本文的学习,你已经掌握了 PageHelper 的核心概念、使用方法和实际应用。从它的起源到现代应用,再到具体的代码实现和最佳实践,每一个环节都进行了详细的讲解。未来,随着 MyBatis 和 PageHelper 的不断发展,分页查询的功能会越来越强大。通过正确理解和使用 PageHelper,开发者可以高效完成分页需求,同时规避潜在问题,提升系统性能与稳定性,写出更加高效、优雅的代码!

image

相关文章:

  • 飞牛NAS本地部署开源TTS文本转语音工具EasyVoice与远程使用流程
  • 前端流行框架Vue3教程:17. _组件数据传递
  • 深入解析HTTP协议演进:从1.0到3.0的全面对比
  • 2025认证杯数学建模第二阶段A题小行星轨迹预测思路+模型+代码
  • 机器学习中采样哪些事
  • React 第四十二节 Router 中useLoaderData的用途详解
  • 牛客网NC22015:最大值和最小值
  • 全面解析机器学习与深度学习中的模型权重文件格式与应用场景
  • 【HarmonyOS 5】鸿蒙mPaaS详解
  • 《Python星球日记》 第80天:目标检测(YOLO、Mask R-CNN)
  • Uniapp 安卓实现讯飞语音听写(复制即用)
  • 隆重推荐(Android 和 iOS)UI 自动化工具—Maestro
  • [数据结构]7. 堆-Heap
  • 单片机-STM32部分:17、数码管
  • Elasticsearch 分片机制高频面试题(含参考答案)
  • 乡村农家游乐小程序源码介绍
  • 【测试工具】selenium和playwright如何选择去构建自动化平台
  • duxapp 2025-01-13 更新 支持小程序配置文件
  • STC8H系列单片机STC8H_H头文件功能注释
  • 【hot100-动态规划-300.最长递增子序列】
  • 获派驻6年后,中国驻厄瓜多尔大使陈国友即将离任
  • 中欧金融工作组第二次会议在比利时布鲁塞尔举行
  • 上海市国防动员办公室副主任吴斌接受审查调查
  • 缺字危机:一本书背后有多少“不存在”的汉字?
  • 从采购到销售!市场监管总局指导行业协会防控肉品风险
  • 中美发布日内瓦经贸会谈联合声明达成关税共识,外交部回应