PageHelper 分页查询【底层代码-图文分析】【原理篇】

754 阅读3分钟

🍰 个人主页:不摆烂的小劉

🍞文章有不合理的地方请各位大佬指正。

🍉文章不定期持续更新,如果我的文章对你有帮助➡️ 关注🙏🏻 点赞👍 收藏⭐️

v2-dbf0ab865e826a7b5f30558e64296b72_720w.jpg

直接上代码,下面是常见的分页应用代码。

PageMethod.startPage(dto.getPageNum(), dto.getPageSize());
List<ProductVO> products = productDAO.listProducts(dto);
return new PageInfo<>(products);

上面三行代码做了什么事?怎么做的?

一、前置知识

1.sql中的limit

limit 10,20 第11 行(因为索引从 0 开始,偏移量 10 就对应第 11 行)开始选取 20 行数据

limit 10 选取 前10 行数据

二、分页原理

1.PageMethod.startPage(dto.getPageNum(), dto.getPageSize());

  • 设置当前要查询的页码(dto.getPageNum())和每页显示的记录数量(dto.getPageSize()

在这里插入图片描述

  • 将分页参数存放到ThreadLocal中:
  • ThreadLocal在线程内直接获取资源,避免方法调用链中频繁地传递分页参数

2.List<ProductVO> products = productDAO.listProducts(dto);

2.1MyBatis拦截器
  • 执行数据库查询,自动应用分页逻辑
  • Pagehelper底层通过拦截器实现分页.参考MyBatis 的拦截器的文档部分,Executor 中的 query 方法可以被拦截,被拦截有两个方法
<E> List<E> query(
      MappedStatement ms,
      Object parameter,
      RowBounds rowBounds,
      ResultHandler resultHandler,
      CacheKey cacheKey,
      BoundSql boundSql) throws SQLException;

<E> List<E> query(
      MappedStatement ms,
      Object parameter,
      RowBounds rowBounds,
      ResultHandler resultHandler) throws SQLException;

6参数query查询, 4参数query 查询

Mybatis通过org.apache.ibatis.session.Configuration 加入拦截,MyBatis 会按照拦截器配置的顺序依次添加到 interceptorChain

public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
}
2.2PageHelper拦截器PageInterceptor

在这里插入图片描述

  • 注解 @Intercepts:定义了一个拦截器,用于拦截MyBatisExecutor类中的query方法。
  • 注解 @Signature:指定了要拦截的方法签名,包括方法类型、方法名和参数类型。
  • 可以看出这里拦截了Executor 中6参数query查询, 4参数query 查询
  • PageInterceptor中的intercept方法是整个分页查询的关键,核心代码如下: ![在这里插入图片描述](i-blog.csdnimg.cn/direct/f1b6… =800x470) 流程图 ![在这里插入图片描述](i-blog.csdnimg.cn/direct/63a8… =500x170) 围绕这块核心代码分析一下:
  1. 判断是否需要进行 count 查询dialect.beforeCount(ms, parameter, rowBounds) 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

image.png

在设置分页参数时,两种构造器,设置`count`是否开启查询总数
//默认开启
this.count = true PageMethod.startPage(dto.getPageNum(), dto.getPageSize()); 
// 关闭查询总数 
PageMethod.startPage(dto.getPageNum(), dto.getPageSize(),false);
  1. 查询总数Long count = count(executor, ms, parameter, rowBounds, null, boundSql); 执行自动创建的 count 查询 在这里插入图片描述 在这里插入图片描述 获取查询总数的sql 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 将查询的sql包起来,拼装count(sql) ,查询总数。

  2. 执行查询resultList = ExecutorUtil.pageQuery(dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey); 在这里插入图片描述

  • sql如何拼接limit?,? 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 获取limit,拼接到查询sql尾部

    在这里插入图片描述

  1. 分页查询后,处理分页结果集return dialect.afterPage(resultList, parameter, rowBounds); 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 将查询到的数据集合集存放到Page集合中 在这里插入图片描述 Page属于List集合

Page类定义 public class Page extends ArrayList implements Closeable 在这里插入图片描述

3.new PageInfo<>(products);

先在PageInfo父类中执行supep(this)计算数据集的总数,再设置分页参数 在这里插入图片描述PageInfo父类中计算总数和设置分页参数都会判断是否属于Page

以上是在底层代码层面解析分页插件Pagehelper的原理.

三、思考

1.为什么将查询数据存放到Page中?

查询结果(即数据列表),还封装了与分页相关的其他信息,比如当前页码、总记录数、每页显示的记录数等

2.是否开启总数查询?

  • 未开启查询 PageMethod.startPage(dto.getPageNum(), dto.getPageSize(),false);

在这里插入图片描述

  • 开启查询总数 PageMethod.startPage(dto.getPageNum(), dto.getPageSize());

    在这里插入图片描述

count(*)与数据量关系InnoDB存储引擎(以 MySQL 为例)

数据量越多查询速度越慢,原因需要读取大量的数据页,涉及更多的磁盘 I/O 操作和 CPU 计算所以比较耗时

31条数据查询0.1s

image.png

500w条数据查询约6.4s

所以是否开启分页,要考虑数据量的影响。

image.png

参考:

pagehelper.github.io/docs/

分页插件PageHelper工作原理_pagehelper底层原理

使用PageHelper实现分页查询

PageHelper原理深度剖析(集成+源码)

彻底搞懂MyBatis插件原理及PageHelper原理

分页查询-PageHelper底层原理分析以及使用PageHelper的步骤

看到这里的友友们 点个赞吧👍👍👍