目录

  • 1、前言
  • 2、基本流程
  • 3、源码分析
    • 3.1、存储分页参数
    • 3.2、改造SQL
    • 3.3、分页查询
    • 3.4、使用 Page 创建 PageInfo 对象

1、前言

PageHelper是mybatis 提供的分页插件,通过PageHelper.startPage(pageNo,pageLimit)就可以帮我们实现分页,目前支持Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六种数据库。

pom依赖:

<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.3.1</version>
</dependency>

yml配置:

pagehelper:helperDialect: mysql #数据库类型,不指定的话会解析 datasource.url进行配置supportMethodsArguments: trueparams: count=countSql

使用示例:

@Service
public class UserService {@Autowiredprivate UserMapper userMapper;/*** 依据用户昵称进行模糊分页查询** @param name* @param page* @param limit* @return*/public PageInfo<User> findPageUsersByName(String name, int page, int limit) {PageHelper.startPage(page, limit);List<User> users = userMapper.selectByName(name);PageInfo<User> pageUsers = new PageInfo<>(users);return pageUsers;}
}

2、基本流程

1、PageHelper向 Mybatis 注册处理分页和 count 的拦截器 PageInterceptor

2、通过 PageHelper.startPage() 方法把分页相关的参数放到 ThreadLcoal 中

3、Mybatis 执行 SQL 过程中会调用拦截器

  • 3.1、根据查询 SQL 构建 count SQL
  • 3.2、从 ThreadLcoal 拿出分页信息,在查询 SQL 后面拼接 limit ?, ?
  • 3.3、清空 ThreadLcoal

4、 使用 Page 创建 PageInfo 对象

3、源码分析

3.1、存储分页参数

直接看PageHelper.startPage,startPage() 方法会构建一个 Page 对象,存储分页相关的参数、设置,最后调用 setLocalPage(page),将其放入ThreadLocal。由于ThreadLocal 每次查询后都会被remove掉,所以一次mapper查询对应一次PageHelper.startPage。

ThreadLocal原理可以看这篇ThreadLocal源码及其内存泄漏问题

    public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {Page<E> page = new Page(pageNum, pageSize, count);page.setReasonable(reasonable);page.setPageSizeZero(pageSizeZero);Page<E> oldPage = getLocalPage();if (oldPage != null && oldPage.isOrderByOnly()) {page.setOrderBy(oldPage.getOrderBy());}setLocalPage(page);return page;}

3.2、改造SQL

PageInterceptor 实现了 org.apache.ibatis.plugin.Interceptor 接口,MyBatis 底层查询其实就是借助SqlSession调用Executor#query,mybatis 在执行查询方法的时候(method = “query”)会调用本拦截器。

/*** Mybatis - 通用分页拦截器*/
@SuppressWarnings({"rawtypes", "unchecked"})
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),}
)
public class PageInterceptor implements Interceptor {// 默认为 Pagehelperprivate volatile Dialect dialect;// count 方法的后缀private String countSuffix = "_COUNT";//  count 查询的缓存,只用于// 本例中 key 为 com.example.pagehelper.dao.UserMapper.selectUsers_COUNTprotected Cache<String, MappedStatement> msCountMap = null;// private String default_dialect_class = "com.github.pagehelper.PageHelper";...
}

进入PageInterceptor.intercept()方法,

@Override
public Object intercept(Invocation invocation) throws Throwable {try {// 获取方法参数Object[] args = invocation.getArgs();MappedStatement ms = (MappedStatement) args[0];Object parameter = args[1];RowBounds rowBounds = (RowBounds) args[2];ResultHandler resultHandler = (ResultHandler) args[3];Executor executor = (Executor) invocation.getTarget();CacheKey cacheKey;BoundSql boundSql;//由于逻辑关系,只会进入一次if (args.length == 4) {//4 个参数时// 拿到原始的查询 SQLboundSql = ms.getBoundSql(parameter);cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);} else {//6 个参数时cacheKey = (CacheKey) args[4];boundSql = (BoundSql) args[5];}checkDialectExists();//对 boundSql 的拦截处理// 实际什么都没做,原样返回了if (dialect instanceof BoundSqlInterceptor.Chain) {boundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.ORIGINAL, boundSql, cacheKey);}List resultList;//调用方法判断是否需要进行分页,如果不需要,直接返回结果if (!dialect.skip(ms, parameter, rowBounds)) {//判断是否需要进行 count 查询if (dialect.beforeCount(ms, parameter, rowBounds)) {// 查询总数// 见 PageInterceptor.count()Long count = count(executor, ms, parameter, rowBounds, null, boundSql);//处理查询总数,返回 true 时继续分页查询,false 时直接返回if (!dialect.afterCount(count, parameter, rowBounds)) {//当查询总数为 0 时,直接返回空的结果return dialect.afterPage(new ArrayList(), parameter, rowBounds);}}// 执行分页查询resultList = ExecutorUtil.pageQuery(dialect, executor,ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);} else {//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);}// 将count、分页 信息放入 ThreadLocalreturn dialect.afterPage(resultList, parameter, rowBounds);} finally {if(dialect != null){dialect.afterAll();}}
}

count
PageInterceptor.count()

private Long count(Executor executor, MappedStatement ms, Object parameter,RowBounds rowBounds, ResultHandler resultHandler,BoundSql boundSql) throws SQLException {// countMsId = "com.example.pagehelper.dao.UserMapper.selectUsers_COUNT"String countMsId = ms.getId() + countSuffix;Long count;//先判断是否存在手写的 count 查询MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);if (countMs != null) {// 直接执行手写的 count 查询count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);} else {// 先从缓存中查if (msCountMap != null) {countMs = msCountMap.get(countMsId);}// 缓存中没有,然后自动创建,并放入缓存if (countMs == null) {//根据当前的 ms 创建一个返回值为 Long 类型的 mscountMs = MSUtils.newCountMappedStatement(ms, countMsId);if (msCountMap != null) {// 放入缓存msCountMap.put(countMsId, countMs);}}// 执行 count 查询count = ExecutorUtil.executeAutoCount(this.dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler);}return count;
}

ExecutorUtil.executeAutoCount()

public static Long executeAutoCount(Dialect dialect, Executor executor, MappedStatement countMs,Object parameter, BoundSql boundSql,RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);//创建 count 查询的缓存 keyCacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);//调用方言获取 count sql:SELECT count(0) FROM user// 见 PageHelper.getCountSql()String countSql = dialect.getCountSql(countMs, boundSql, parameter, rowBounds, countKey);//countKey.update(countSql);BoundSql countBoundSql = new BoundSql(countMs.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);//当使用动态 SQL 时,可能会产生临时的参数,这些参数需要手动设置到新的 BoundSql 中for (String key : additionalParameters.keySet()) {countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));}//对 boundSql 的拦截处理if (dialect instanceof BoundSqlInterceptor.Chain) {countBoundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.COUNT_SQL, countBoundSql, countKey);}//执行 count 查询Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);Long count = (Long) ((List) countResultList).get(0);return count;
}

3.3、分页查询

ExecutorUtil.pageQuery

public static <E> List<E> pageQuery(Dialect dialect, Executor executor, MappedStatement ms, Object parameter,RowBounds rowBounds, ResultHandler resultHandler,BoundSql boundSql, CacheKey cacheKey) throws SQLException {//判断是否需要进行分页查询if (dialect.beforePage(ms, parameter, rowBounds)) {//生成分页的缓存 keyCacheKey pageKey = cacheKey;//处理参数对象parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);//调用方言获取分页 sql,这里是重点,是添加 limit 的地方// pageSql = select id, name from user LIMIT ?, ? String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);//设置动态参数for (String key : additionalParameters.keySet()) {pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));}//对 boundSql 的拦截处理if (dialect instanceof BoundSqlInterceptor.Chain) {pageBoundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.PAGE_SQL, pageBoundSql, pageKey);}//执行分页查询return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);} else {//不执行分页的情况下,也不执行内存分页return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);}
}

3.4、使用 Page 创建 PageInfo 对象

最后使用PageInfo<User> pageUsers = new PageInfo<>(users)获取到分页信息

PageHelper是怎么分页的相关推荐

  1. SpringBoot Mybatis解决使用PageHelper一对多分页问题

    SpringBoot Mybatis解决使用PageHelper一对多分页问题 参考文章: (1)SpringBoot Mybatis解决使用PageHelper一对多分页问题 (2)https:// ...

  2. SpringBoot-07:SpringBoot整合PageHelper做多条件分页查询

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 本篇博客讲述如何在SpringBoot中整合PageHelper,如何实现带多个条件,以及PageInfo中的 ...

  3. PageHelper分页插件失效的原因,PageHelper手写分页用法

    导包错误(以下是正确的) import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; PageHel ...

  4. pageHelper没有正确分页,sql拼接多加limit等参数。

    pageHelper没有正确分页,sql拼接多加limit等参数. 背景: 日常敲代码,发现用了pageHelper真的方便,尤其是使用了pageInfo这个类,不用自己写工具类了,直接将所有的页码信 ...

  5. pagehelper联表分页查询

    (springboot2.0.1.pagehelper5.1.4) pagehelper联表分页,会默认在sql语句的后面添加 limit ?,进行分页.如果是复杂的sql联表查询结果远远不能满足我们 ...

  6. Mybatis插件原理和PageHelper结合实战分页插件(七)

    今天和大家分享下mybatis的一个分页插件PageHelper,在讲解PageHelper之前我们需要先了解下mybatis的插件原理.PageHelper 的官方网站:https://github ...

  7. SSM中使用Mybatis的PageHelper插件实现分页

    效果 实现 前言 前面实现SSM整合以及实现原始手动分页参考 https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/85113289 添加 ...

  8. springboot+thymeleaf+pageHelper带条件分页查询

    html层 <div><a class="num"><b th:text="'共 '+ ${result.resultMap['pages' ...

  9. java 分页_Spring Boot + MyBatis 如何借助PageHelper插件实现分页效果

    概述 上文中已经介绍了Spring和MyBatis的整合,在上文的基础上我们加入了PageHelper这个插件,来实现MyBatis列表查询的分页效果 PageHelper是啥 PageHelper是 ...

  10. layui结合mybatis的pagehelper插件的分页通用的方法

    总体思路: 1.前台查询的时候将当前页和页大小传到后台 2.后台将当前页,页大小以及数据与数据总数返回前台,前台显示完表格完数据之后显示分页插件. 前台页面: 准备查询条件的表单,与数据表格,分页di ...

最新文章

  1. 图的遍历——DFS(邻接矩阵)
  2. 【Linux网络编程】IP地址介绍
  3. C# DateTimePicker控件设置空时间
  4. 让人期待的Visual Studio 2010
  5. [Mac]一些命令技巧
  6. kail利用msf工具对MS12-020漏洞进行渗透测试
  7. python怎么获取lol皮肤名称_英雄联盟手游免费皮肤获取攻略 LOL免费皮肤怎么得...
  8. Delphi2010
  9. # AD19规则设置的傻瓜式教程
  10. Linux c/c++开发常用头文件
  11. java毕业设计_基于ssm的毕业设计管理系统
  12. 点击按钮显示明文密码
  13. 单点登录系统CAS入门
  14. UVM——TLM通信机制(port、export、imp + analysis_port)
  15. SEO基础知识完美教程
  16. EXCEL表格中如何给奇偶数行填充不同颜色
  17. mysql与mysqld
  18. 响应式编程android,Android响应式编程(一)RxJava[入门基础]
  19. kuka机器人offset指令_KUKA机器人MADA详解.doc
  20. 网上赚钱日结工资正规,看懂就去操作吧!

热门文章

  1. Map集合常用方法(一)
  2. win10兼容模式怎么设置_系统不兼容怎么办 Bios设置ide兼容模式
  3. ARM异常处理(3):Bus faults、Memory management faults、Usage faults、Hard faults详解
  4. python将多个txt文件导入一个excel的不同sheet中
  5. 如何学习IPv6安全
  6. 如何将adobe pdf背景设置为护眼色
  7. 「完整版」小说《倾心倾情倾了所有》在线阅读
  8. JS 内存泄漏的几种情况以及解决方案
  9. 火绒怎么修复dll文件丢失?
  10. 介绍一款密码分析软件cap4