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 分页原理 的相关文章

  • 过滤两次 Lambda Java

    我有一个清单如下 1 2 3 4 5 6 7 和 预期结果必须是 1 2 3 4 5 6 7 我知道怎么做才能到7点 我的结果 1 2 3 4 5 6 我也想知道如何输入 7 我添加了i gt i objList size 1到我的过滤器
  • 如何更改javaFX中按钮的图像?

    我正在使用javaFX 我制作了一个按钮并为此设置了图像 代码是 Image playI new Image file c Users Farhad Desktop icons play2 jpg ImageView iv1 new Ima
  • Java 公历日历更改时区

    我正在尝试设置 HOUR OF DAY 字段并更改 GregorianCalendar 日期对象的时区 GregorianCalendar date new GregorianCalendar TimeZone getTimeZone GM
  • Java 集合的并集或交集

    建立并集或交集的最简单方法是什么Set在 Java 中 我见过这个简单问题的一些奇怪的解决方案 例如手动迭代这两个集合 最简单的单行解决方案是这样的 set1 addAll set2 Union set1 retainAll set2 In
  • 从最终实体获取根证书和中间证书

    作为密码学的菜鸟 我每天都会偶然发现一些简单的事情 今天只是那些日子之一 我想用 bouncy castle 库验证 java 中的 smime 消息 我想我几乎已经弄清楚了 但此时的问题是 PKIXparameters 对象的构建 假设我
  • 没有 Spring 的自定义 Prometheus 指标

    我需要为 Web 应用程序提供自定义指标 问题是我不能使用 Spring 但我必须使用 jax rs 端点 要求非常简单 想象一下 您有一个包含键值对的映射 其中键是指标名称 值是一个简单的整数 它是一个计数器 代码会是这样的 public
  • 在 junit 测试中获取 javax.lang.model.element.Element 类

    我想测试我的实用程序类 ElementUtils 但我不知道如何将类作为元素获取 在 AnnotationProcessors 中 我使用以下代码获取元素 Set
  • Eclipse Maven Spring 项目 - 错误

    I need help with an error which make me crazy I started to study Java EE and I am going through tutorial on youtube Ever
  • Hibernate 的 PersistentSet 不使用 hashCode/equals 的自定义实现

    所以我有一本实体书 public class Book private String id private String name private String description private Image coverImage pr
  • 内部类的构造函数引用在运行时失败并出现VerifyError

    我正在使用 lambda 为内部类构造函数创建供应商ctx gt new SpectatorSwitcher ctx IntelliJ建议我将其更改为SpectatorSwitcher new反而 SpectatorSwitcher 是我正
  • SQL 最近日期

    我需要在 php 中获取诸如 2010 04 27 之类的日期作为字符串 并在表中找到最近的 5 个日期 表中的日期保存为日期类型 您可以使用DATEDIFF http dev mysql com doc refman 5 1 en dat
  • 不接受任何内容也不返回任何内容的函数接口[重复]

    这个问题在这里已经有答案了 JDK中是否有一个标准的函数式接口 不接受也不返回任何内容 我找不到一个 像下面这样 FunctionalInterface interface Action void execute 可运行怎么样 Functi
  • 关键字“table”附近的语法不正确,无法提取结果集

    我使用 SQL Server 创建了一个项目 其中包含以下文件 UserDAO java public class UserDAO private static SessionFactory sessionFactory static se
  • 最新的 Hibernate 和 Derby:无法建立 JDBC 连接

    我正在尝试创建一个使用 Hibernate 连接到 Derby 数据库的准系统项目 我正在使用 Hibernate 和 Derby 的最新版本 但我得到的是通用的Unable to make JDBC Connection error 这是
  • Android:无法使用 DbHelper 和 Contract 类将数据插入 SQLite

    public class Main2Activity extends AppCompatActivity private EditText editText1 editText2 editText3 editText4 private Bu
  • MySQL 转储未知选项“-no-beep”

    在旧服务器上我使用了mysql转储命令来备份 MySQL 数据库 在新服务器上 MySQL 版本为 5 6 相同的命令给出了错误 unknown option no beep 无论它插入什么 我也在互联网上搜索过 但找不到任何帮助 在 my
  • 如何将双精度/浮点四舍五入为二进制精度?

    我正在编写对浮点数执行计算的代码的测试 不出所料 结果很少是准确的 我想在计算结果和预期结果之间设置一个容差 我已经证实 在实践中 使用双精度 在对最后两位有效小数进行四舍五入后 结果始终是正确的 但是usually四舍五入最后一位小数后
  • CamcorderProfile.videoCodec 返回错误值

    根据docs https developer android com reference android media CamcorderProfile html 您可以使用CamcorderProfile获取设备默认视频编解码格式 然后将其
  • Spring Boot 无法更新 azure cosmos db(MongoDb) 上的分片集合

    我的数据库中存在一个集合 documentDev 其分片键为 dNumber 样本文件 id 12831221wadaee23 dNumber 115 processed false 如果我尝试使用以下命令通过任何查询工具更新此文档 db
  • Spring Rest 和 Jsonp

    我正在尝试让我的 Spring Rest 控制器返回jsonp但我没有快乐 如果我想返回 json 但我有返回的要求 完全相同的代码可以正常工作jsonp我添加了一个转换器 我在网上找到了用于执行 jsonp 转换的源代码 我正在使用 Sp

随机推荐

  • 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 的编写时间 另一方面借助其强大的条件构造器可以缩减大多数