PageHelper 分页原理

2023-11-06

本文主要介绍了开源的mybatis分页插件 PageHelper 工作原理。

现在的项目开发基本离不开Mybatis,一方面是借助增强版的 Mybatis Plus节省许多基础 SQL 的编写时间,另一方面借助其强大的条件构造器可以缩减大多数场景下的业务 SQL 编写时间。

Mybatis使用当中几乎都会搭配上一个分页插件,那就是 PageHelper插件,插件使用很简单,加上依赖,配置拦截器,然后在执行 mapper查询之前使用 PageHelper.startPage(1, 10);即可开启分页查询,最后返回的对象是一个继承自ListPage对象,可参考PageInfo对象的构造方法查看分页对象的数据含义。

直接查看源码,PageHelper.startPage(1, 10);最终会调到PageMethod类这来,将参数组装成一个 Page 对象放到 ThreadLocal变量中。下面是关键代码:

/**
     * 开始分页
     *
     * @param pageNum      页码
     * @param pageSize     每页显示数量
     * @param count        是否进行count查询
     * @param reasonable   分页合理化,null时用默认配置
     * @param pageSizeZero true且pageSize=0时返回全部结果,false时分页,null时用默认配置
     */
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());
    }
    setLocalPage(page);
    return page;
}
    
/**
     * 设置 Page 参数
     *
     * @param page
     */
protected static void setLocalPage(Page page) {
    LOCAL_PAGE.set(page);
}

protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();

接下来的事情就是 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 个参数时
                boundSql = 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)) {
                    //查询总数
                    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);
            }
            return dialect.afterPage(resultList, parameter, rowBounds);
        } finally {
            if(dialect != null){
                dialect.afterAll();
            }
        }
    }

会在一系列处理后用 dialect对象做是否需要分页的判断

if (!dialect.skip(ms, parameter, rowBounds))

默认的dialect对象其实就是com.github.pagehelper.PageHelper的实例,判断是否需要分页的关键代码如下:

    @Override
    public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
        if (ms.getId().endsWith(MSUtils.COUNT)) {
            throw new RuntimeException("在系统中发现了多个分页插件,请检查系统配置!");
        }
        Page page = pageParams.getPage(parameterObject, rowBounds);
        if (page == null) {
            return true;
        } else {
            //设置默认的 count 列
            if (StringUtil.isEmpty(page.getCountColumn())) {
                page.setCountColumn(pageParams.getCountColumn());
            }
            autoDialect.initDelegateDialect(ms);
            return false;
        }
    }

如果要分页,那么接着判断是否查询总数,需要查询总数的话,则会先查询是否有自定义的 count SQL,如果没有的话会自己构建一个,默认查询总数的方式是去掉原 SQL 的无关排序然后包一层 count(0) 的外层查询。


    

    private Long count(Executor executor, MappedStatement ms, Object parameter,
                       RowBounds rowBounds, ResultHandler resultHandler,
                       BoundSql boundSql) throws SQLException {
        String countMsId = ms.getId() + countSuffix;
        Long count;
        //先判断是否存在手写的 count 查询
        MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);
        if (countMs != null) {
            count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
        } else {
            if (msCountMap != null) {
                countMs = msCountMap.get(countMsId);
            }
            //自动创建
            if (countMs == null) {
                //根据当前的 ms 创建一个返回值为 Long 类型的 ms
                countMs = MSUtils.newCountMappedStatement(ms, countMsId);
                if (msCountMap != null) {
                    msCountMap.put(countMsId, countMs);
                }
            }
            count = ExecutorUtil.executeAutoCount(this.dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler);
        }
        return count;
    }

接下来就是对原SQL的分页,会根据SQL方言添加分页参数,可以从 AbstractHelperDialect子类中看到支持的方言,下面是Mysql 方言的分页参数

    @Override
    public String getPageSql(String sql, Page page, CacheKey pageKey) {
        StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
        sqlBuilder.append(sql);
        if (page.getStartRow() == 0) {
            sqlBuilder.append("\n LIMIT ? ");
        } else {
            sqlBuilder.append("\n LIMIT ?, ? ");
        }
        return sqlBuilder.toString();
    }

结合上面的代码可以看到,拿到原 BoundSql ,默认添加分页参数,组装成新的 BoundSql 交给 mybatis,至此分页查询已经完成。

    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)) {
            //生成分页的缓存 key
            CacheKey pageKey = cacheKey;
            //处理参数对象
            parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
            //调用方言获取分页 sql
            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);
        }
    }

最后处理分页结果,在拦截器末尾可以看到这行代码,这里的 dialect 默认还是 PageHelper,经过处理可以看到分页查询返回的是一个 Page对象,至此整个分页处理流程完毕。

return dialect.afterPage(resultList, parameter, rowBounds);

    @Override
    public Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds) {
        Page page = getLocalPage();
        if (page == null) {
            return pageList;
        }
        page.addAll(pageList);
        if (!page.isCount()) {
            page.setTotal(-1);
        } else if ((page.getPageSizeZero() != null && page.getPageSizeZero()) && page.getPageSize() == 0) {
            page.setTotal(pageList.size());
        } else if (page.isOrderByOnly()) {
            page.setTotal(pageList.size());
        }
        return page;
    }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

PageHelper 分页原理 的相关文章

  • 将SQL数据引入jquery availabletag

    我正在尝试制作自动完成文本框 但如何将 SQL 数据包含到 jquery 可用标记并循环它 我无法根据以下代码执行该功能 任何帮助 将不胜感激 谢谢 这是我的预期输出 预期结果演示 http jsfiddle net VvETA 71 jq
  • 您建议使用哪种压缩(GZIP 是最流行的)servlet 过滤器?

    我正在寻找一个用于大容量网络应用程序的 GZIP servlet 过滤器 我不想使用容器特定的选项 要求 能够压缩响应负载 XML Faster 已在大批量应用的生产中得到验证 应适当设置适当内容编码 跨容器移植 可选择解压缩请求 谢谢 我
  • FileNotFoundException - Struts2 文件上传

    Strange FileNotFoundException使用Struts2上传文件时 这是 JSP 的一部分
  • 为什么 MOVE CURSOR 在 OS X Mountain Lion 上不显示?

    我正在做一个项目 想看看 Swing 提供的每个光标是什么样子的 public class Test public static void main String args JFrame frame new JFrame frame set
  • Spring数据中的本机查询连接

    我有课 Entity public class User Id Long id String name ManyToMany List
  • Java中的断点和逐步调试?

    抱歉我的问题名称很奇怪 我不知道如何寻找这个 因为我不知道这些东西是如何称呼的 Visual Studio 中至少有一个功能 您可以单击代码左侧并设置一个大红点的起点 然后运行程序 您可以通过按 f8 或 f5 实际上是不同的 f 来跟踪步
  • 具有“日期之间”的 CakePHP 模型

    我有一个很大的数据集 超过十亿行 数据在数据库中按日期分区 因此 我的查询工具必须在每个查询上指定一个 SQL Between 子句 否则它将必须扫描每个分区 而且 它会在返回之前超时 所以 我的问题是 分区的数据库中的字段是日期 使用 C
  • 如何通过注解用try-catch包装方法?

    如果应该在方法调用中忽略异常 则可以编写以下内容 public void addEntryIfPresent String key Dto dto try Map
  • 添加到列表时有没有办法避免循环?

    我想知道这样的代码 List
  • 批处理文件并与数据库比较

    目前我正在开发一个 Spring Boot 应用程序 该应用程序定期尝试处理包含用户数据的文件 其中每行都包含userId and departamentId隔开 例如123534 13 该文件将包含数百万条记录 我的要求是以这样的方式将此
  • Java:如何确定文件所在的驱动器类型?

    Java 是否有一种独立于平台的方法来检测文件所在的驱动器类型 基本上我有兴趣区分 硬盘 可移动驱动器 如 USB 记忆棒 和网络共享 JNI JNA 解决方案不会有帮助 可以假设 Java 7 您可以使用 Java 执行 cmd fsut
  • JAVA中遍历JSON数据

    我是 JSON 新手 我使用 HTTPUrlConnections 并在 JAVA 程序中获得一些响应 响应数据将类似于 data id 1 userId 1 name ABC modified 2014 12 04 created 201
  • 避免 Java 中的重复导入:继承导入?

    有没有办法 继承 导入 Example 常见枚举 public enum Constant ONE TWO THREE 使用此枚举的基类 public class Base protected void register Constant
  • 控制数据是否存在于数组中

    我在mysql中有两个不同的表 我正在使用curl从json文件中获取数据 我的第一个表名称是 tblclients 该表存储客户端数据 我的第二个表名称是 tblcustomfieldsvalues 该表使用 tblclients 表的
  • 如何从 Ant 启动聚合 jetty-server JAR?

    背景 免责声明 I have veryJava 经验很少 我们之前在 Ant 构建期间使用了 Jetty 6 的包装版本来处理按需静态内容 JS CSS 图像 HTML 因此我们可以使用 PhantomJS 针对 HTTP 托管环境运行单元
  • parent_id 是外键(自引用)并且为 null?

    浏览 Bill Karwin 的书 SQL Antipatterns 第 3 章 Naive Trees 邻接表 父子关系 有一个注释表的示例 CREATE TABLE Comments comment id SERIAL PRIMARY
  • 如何处理 StaleElementReferenceException

    我正在为鼠标悬停工作 我想通过使用 for 循环单击每个链接来测试所有链接的工作条件 在我的程序中 迭代进行一次 而对于下一次迭代 它不起作用并显示 StaleElementReferenceException 如果需要 请修改代码 pub
  • 使用 Java https 上传到 Imgur v3 错误

    我目前正在尝试使用他们当前的 API v3 上传到 imgur 但是我不断收到错误 错误 javax net ssl SSLException 证书中的主机名不匹配 api imgur com imgur com OR imgur com
  • Mysql加密/存储敏感数据,

    我的 PHP 网站有以下内容 启用 SSL 饼干 session set cookie params cookieParams lifetime cookieParams path cookieParams domain secure ht
  • 基于 Spring Boot 的测试中的上下文层次结构

    我的 Spring Boot 应用程序是这样启动的 new SpringApplicationBuilder sources ParentCtxConfig class child ChildFirstCtxConfig class sib

随机推荐

  • ChatGPT写文章教程_如何使用ChatGPT写文章

    如何用ChatGPT写出高质量文章 ChatGPT是一种基于人工智能技术的自然语言处理工具 它可以生成高质量的文章 但是如何利用它来写出优秀的文章呢 本文将为你介绍如何使用ChatGPT来写出高质量的文章 一 什么是ChatGPT Chat
  • MySQL数据库——DML基本操作

    文章目录 前言 插入数据 全列插入 指定列插入 修改数据 删除数据 前言 前面我们学习了MySQL DDL操作 对数据库和表的结构的操作 那么今天我将为大家分享MySQL DML操作 对表数据的操作 MySQL DML操作有以下几种 插入操
  • linux下的定时任务

    linux环境下 我们有两种常用的方式实现定时的任务 等待一段时间后执行某任务 一次性 该类型的需求直接通过sleep方法即可完成 1小时候执行ls任务 并将输出重定向至ls log sleep 1h ls gt gt ls log 每日定
  • 对话Sam Altman与Greg Brockman:初心和过去,信念和现在,责任和未来

    导读 近日 硅谷著名投资人Reid Hoffman和Aria Finger联手对Sam Altman和Greg Brockman进行了一场访谈 访谈涉及到主题有 OpenAI的使命 人工智能对教育 医疗等行业的变革性影响 人工智能如何面对监
  • 涂鸦TuyaOS SDK 网关例程-wifi配网

    涂鸦TuyaOS SDK 网关例程 wifi配网 使能WIFI配网预编译宏定义 net tools相关文件路径 配网步骤 如果一直都没配网成功 看下面 总结 使能WIFI配网预编译宏定义 例程默认没开WiFi的demo使能 代码预编译开启使
  • 大学计算机python怎样一个星期速成?

    一 Python基础知识 黑马程序员Python入门教程 适合零基础自学的Python教程 1 学习内容 计算机组成原理 计算机组成部分 操作系统分类 B S和C S架构 理解什么是软件和硬件 Python编程规范 字符串 字典 列表 元组
  • linux网络配通

    Linux网络配置 vim etc sysconfig network scripts ifcfg ens33 ONBOOT yes DNS 8 8 8 8 IPADDR 192 168 118 90 NETMASK 255 255 255
  • 最新版本EMGU使用说明 V4.2

    最新版本EMGU使用说明 V4 2 项目打开nuget搜索 Emgu CV Emgu CV runtime windows 下载安装 说明 netstandard 2 0环境版本 Emgu CV runtime windows 为windo
  • layui的框架导入

    目录 1 导入步骤 2 框架包含的组件 1 导入步骤 1 1 下载框架包 http www layui com 1 2 将layui文件夹拷入项目位置 1 3 页面中导入 layui all js layui css 和 jquery 即可
  • docker启动Nacos docker run requires at least 1 argument.

    nacos启动命令 docker run d e PREFER HOST MODE ip e MODE standalone e SPRING DATASOURCE PLATFORM mysql e MYSQL SERVICE HOST 4
  • glide的基本使用及进阶使用

    glide生命周期管理 内存管理 缓存机制 加载器 观察者模式 前言 本系列博文主要通过借鉴其他优秀博文以及自己的实际开发经验综合得出 主要分析glide相关使用 机制以及设计模式等内容 相信这篇博文会让你对glide有更加深入的理解 本篇
  • 如何手动优化xp系统

    1 删除系统备份的文件 直接在 开始 菜单的 运行 对话框中 输入sfc exe purgecache就可以了 2 删除系统中的帮助文件 如果你没有看帮助的习惯 况且在有时候误操作的时候 常把帮助对话框调出来 就删除了吧 免得浪费空间 找到
  • python项目快速生产依赖库列表

    安装pipreqs库 pip install pipreqs 在项目工程根目标执行以下命令 生成requirements txt pipreqs encoding utf8
  • Debian 8.2 安装docker

    一 安装docker io包之前 需要先设置使用backports源 编辑 etc apt sources list文件 加入下面这一句 deb http http debian net debian jessie backports ma
  • Python进阶-函数、模块和包

    使用如下方法 可以使函数接受不定数目的参数 def add x args total x for arg in args total arg return total 这样定义的函数不能使用关键词传入参数 要使用关键词 可以这样 def a
  • 【Dongle】【数据结构】Linklist L、Linklist *L、Node *p 和Node p

    最近在做数据结构导论试题的时候无意中发现答案中有用到这些东西的Linklist L Linklist L Node p 和Node p 不过不清楚都具体指什么 故而从网上查找了一番 先定义一个单链表结构体 typedef struct no
  • 01-linux下Postgresql的安装

    1 看官网 https www postgresql org download 2 选择系统 这里选择Red Hat family Linux including CentOS Fedora Scientific Oracle varian
  • 量子计算机编程_对量子计算机进行编程

    量子计算机编程 Quantum computing is one of the most rapidly advancing technologies Many companies and research labs are racing
  • AI常见部署方式

    文章目录 1 AI部署简介 2 具体部署 1 AI部署简介 没人告诉你的大规模部署AI高效流程 对大部分 TensorFlow 模型来说 部署流程是相同的 1 将图像固化为 Protobuf 二进制文件 2 调整推断代码 使它可以处理固化的
  • PageHelper 分页原理

    本文主要介绍了开源的mybatis分页插件 PageHelper 工作原理 现在的项目开发基本离不开Mybatis 一方面是借助增强版的 Mybatis Plus节省许多基础 SQL 的编写时间 另一方面借助其强大的条件构造器可以缩减大多数