PageHelper 分页逻辑 源码解析

一、PageHelper

PageHelper 是一个用于在 MyBatis 中进行分页查询的开源分页插件。它能够方便地帮助开发者处理分页查询的逻辑,简化代码,并提高开发效率。PageHelper 支持多种数据库,包括 MySQL、Oracle、PostgreSQL 等。

PageHelper 的实现,主要依赖于 ThreadLocalMybatis拦截器,它通过自定义拦截器,在执行 SQL 语句前后进行拦截和处理,实现分页的功能。

下面一起看下PageHelper 分页的实现逻辑。

二、PageHelper.startPage

PageHelper.startPage 主要会调用 PageMethod 中的 startPage 方法:

在这里插入图片描述

在这里插入图片描述

主要逻辑是通过 getLocalPagesetLocalPage 获取分页信息和设置分页信息,实际是利用的 ThreadLocal,可以从 getLocalPage 方法中看出:

在这里插入图片描述
在这里插入图片描述

其实 PageHelper.startPage 就是向 ThreadLocal 中记录了分页信息,那记录的信息在什么时候使用呢,就要看到 Mybatis 中的拦截器了。

三、PageInterceptor

这个拦截器在 com.github.pagehelper 下的 PageInterceptor

@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 {
    private volatile Dialect dialect;
    private String countSuffix = "_COUNT";
    protected Cache<String, MappedStatement> msCountMap = null;
    private String default_dialect_class = "com.github.pagehelper.PageHelper";

    @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();

            List resultList;
            //调用方法判断是否需要进行分页,如果不需要,直接返回结果
            if (!dialect.skip(ms, parameter, rowBounds)) {
                //判断是否需要进行 count 查询
                if (dialect.beforeCount(ms, parameter, rowBounds)) {
                    //查询总数
                    Long count = count(executor, ms, parameter, rowBounds, resultHandler, 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();
            }
        }
    }

    /**
     * Spring bean 方式配置时,如果没有配置属性就不会执行下面的 setProperties 方法,就不会初始化
     * <p>
     * 因此这里会出现 null 的情况 fixed #26
     */
    private void checkDialectExists() {
        if (dialect == null) {
            synchronized (default_dialect_class) {
                if (dialect == null) {
                    setProperties(new Properties());
                }
            }
        }
    }

    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 {
            countMs = msCountMap.get(countMsId);
            //自动创建
            if (countMs == null) {
                //根据当前的 ms 创建一个返回值为 Long 类型的 ms
                countMs = MSUtils.newCountMappedStatement(ms, countMsId);
                msCountMap.put(countMsId, countMs);
            }
            count = ExecutorUtil.executeAutoCount(dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler);
        }
        return count;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        //缓存 count ms
        msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);
        String dialectClass = properties.getProperty("dialect");
        if (StringUtil.isEmpty(dialectClass)) {
            dialectClass = default_dialect_class;
        }
        try {
            Class<?> aClass = Class.forName(dialectClass);
            dialect = (Dialect) aClass.newInstance();
        } catch (Exception e) {
            throw new PageException(e);
        }
        dialect.setProperties(properties);

        String countSuffix = properties.getProperty("countSuffix");
        if (StringUtil.isNotEmpty(countSuffix)) {
            this.countSuffix = countSuffix;
        }
    }

}

主要看到这里的 intercept 方法,首先获取到绑定的SQL语句,然后判断是否需要查询总数,默认情况下是true,如果需要查询总数的话,先进行总数查询,如果总数为 0 则直接返回空结果,否则的话通过 ExecutorUtil.pageQuery 进行分页查询,下面可以看下 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)) {
        //生成分页的缓存 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));
        }
        //执行分页查询
        return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
    } else {
        //不执行分页的情况下,也不执行内存分页
        return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
    }
}

这里的主要逻辑是对 SQL 进行了包装,其实就是拼接上分页参数和排序参数,如果需要排序的话,然后重新组装成BoundSql,进行MyBatis 的后续处理,这里看下SQL是如何包装的,看到 dialect.getPageSql 方法:

public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
    String sql = boundSql.getSql();
    Page page = getLocalPage();
    //支持 order by
    String orderBy = page.getOrderBy();
    if (StringUtil.isNotEmpty(orderBy)) {
        pageKey.update(orderBy);
        sql = OrderByParser.converToOrderBySql(sql, orderBy);
    }
    if (page.isOrderByOnly()) {
        return sql;
    }
    return getPageSql(sql, page, pageKey);
}

这里看到了熟悉的 getLocalPage 方法,从 ThreadLocal 中获取分页信息,如果需要排序的话则使用 OrderByParser.converToOrderBySql 拼接排序的参数,最后是通过 getPageSql 方法拼接组装分页的参数。

接着看到 getPageSql 方法 :

@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(" LIMIT ? ");
    } else {
        sqlBuilder.append(" LIMIT ?, ? ");
    }
    return sqlBuilder.toString();
}

这里就比较明显了,通过拼接 LIMIT 进行分页,并且判断如果是第一页的话仅拼接数量即可,在这里组装分页参数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/354760.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

了解OpenCV的数据类型

OpenCV是一个开源的计算机视觉库&#xff0c;广泛应用于图像和视频处理领域。在OpenCV中&#xff0c;数据类型扮演着非常重要的角色&#xff0c;它们决定了数据的存储方式和操作方式。本文将介绍OpenCV中常见的数据类型&#xff0c;包括图像数据类型、矩阵数据类型和轮廓数据类…

使用Python和ffmpeg旋转WebM视频并保存为MP4文件

简介: 在本篇博客中&#xff0c;我们将介绍如何使用Python编写一个程序&#xff0c;结合wxPython和ffmpeg模块&#xff0c;来旋转WebM视频文件并将其保存为MP4格式。我们将使用wxPython提供的文件选择对话框来选择输入和输出文件&#xff0c;并使用ffmpeg库来进行视频旋转操作。…

IS-IS:09 ISIS路由过滤

在IS-IS 网络中&#xff0c;有时需要使用 filter-policy 工具对 IS-IS 路由进行过滤。这里所说的过滤&#xff0c;是指路由器在将自己IS-IS 路由表中的某些 IS-IS 路由纳入进自己的 IP 路由表的过程&#xff0c;一些满足了过滤条件的 IS-IS 路由将被限制纳入 IP 路由表中。 需要…

程序员该懂的一些测试(四)测试覆盖率

测试覆盖率通常被用来衡量测试的充分性和完整性&#xff0c;从广义的角度来讲&#xff0c;测试覆盖率主要分 为两大类&#xff0c;一类是面向项目的需求覆盖率&#xff0c;另一类是更偏向技术的代码覆盖率。 需求覆盖率 需求覆盖率是指测试对需求的覆盖程度&#xff0c;通常的…

Linux 驱动开发基础知识——总线设备驱动模型(七)

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;Vir2021GKBS &#x1f43c;本文由…

C++ //练习 3.5 编写一段程序从标准输入中读入多个字符串并将它们连接在一起,输出连接成的大字符串。然后修改上述程序,用空格把输入的多个字符串分隔开来。

C Primer&#xff08;第5版&#xff09; 练习 3.5 练习 3.5 编写一段程序从标准输入中读入多个字符串并将它们连接在一起&#xff0c;输出连接成的大字符串。然后修改上述程序&#xff0c;用空格把输入的多个字符串分隔开来。 环境&#xff1a;Linux Ubuntu&#xff08;云服务…

.NET高级面试指南专题三【线程和进程】

在C#中&#xff0c;线程&#xff08;Thread&#xff09;和进程&#xff08;Process&#xff09;是多任务编程中的重要概念&#xff0c;它们用于实现并发执行和多任务处理。 进程&#xff08;Process&#xff09;&#xff1a; 定义&#xff1a; 进程是正在运行的程序的实例&…

ThinkPhp3.2(qidian)部署文档

宝塔环境部署 申请域名以及域名解析 具体配置&#xff0c;可百度之 在宝塔面板中创建网站 上传代码导入数据配置运行目录 注意&#xff1a;&#xff08;如果版本&#xff1a;thinkphp3.2 &#xff09;配置 运行目录要特别注意&#xff1a;运行目录要选择根目录“/”&#xff…

【c++】类和对象 - 类的引入和定义

1.类的引入 C语言结构体中只能定义变量&#xff0c;在C中&#xff0c;结构体内不仅可以定义变量&#xff0c;也可以定义函数。比如&#xff1a;之前在数据结构初阶中&#xff0c;用C语言方式实现的栈&#xff0c;结构体中只能定义变量&#xff1b;现在以C方式实现&#xff0c;…

报错:AttributeError: ‘str‘ object has no attribute ‘decode‘

original_keras_version f.attrs[‘keras_version’].decode(‘utf8’) AttributeError: ‘str’ object has no attribute ‘decode’ 1、问题描述 original_keras_version f.attrs[keras_version].decode(utf8) AttributeError: str object has no attribute decode2、原…

【英语趣味游戏】填字谜(Crossword)第1天

谜题出处 柯林斯字谜大全&#xff08;6&#xff09;&#xff0c;Collins——Big Book of Crosswords&#xff08;Book 6&#xff09; Puzzle Number: 114 本期单词 横向 1、Situation involving danger (4) 包含危险的情境&#xff0c;4个字母 答案&#xff1a;Risk&#xff…

Spark写入kafka(批数据和流式)

Spark写入&#xff08;批数据和流式处理&#xff09; Spark写入kafka批处理 写入kafka基础 # spark写入数据到kafka from pyspark.sql import SparkSession,functions as Fss SparkSession.builder.getOrCreate()# 创建df数据 df ss.createDataFrame([[9, 王五, 21, 男], […

面试篇-SpringBoot自动配置原理

在Spring Boot中&#xff0c;自动装配是一种强大的功能&#xff0c;它允许开发者快速、简单地配置和管理应用程序的组件。以下是对Spring Boot自动装配原理的详细解释&#xff1a; Spring BootApplication注解源码&#xff1a; SpringBootApplication注解是一个复合注解&#x…

leetcode—课程表 拓扑排序

1 题目描述 你这个学期必须选修 numCourses 门课程&#xff0c;记为 0 到 numCourses - 1 。 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出&#xff0c;其中 prerequisites[i] [ai, bi] &#xff0c;表示如果要学习课程 ai 则 必须 先学习课程 …

旋转编码器SIQ-02FVS3驱动(AuroraFOC)

一. 简介 本次将基于AuroraFOC开发板&#xff0c;来教大家如何将旋转编码器按键优雅地使用起来&#xff0c;为大家开发多功能按键提供一种思路。 开发环境 STM32CubeMX HAL库Clion 作者: FPGA之旅(ValentineHP) 二. 原理(图)介绍 旋转编码器按键原理图如下&#xff0c;它…

《动手学深度学习(PyTorch版)》笔记3.1

Chapter3 Linear Neural Networks 3.1 Linear Regression 3.1.1 Basic Concepts 我们通常使用 n n n来表示数据集中的样本数。对索引为 i i i的样本&#xff0c;其输入表示为 x ( i ) [ x 1 ( i ) , x 2 ( i ) , . . . , x n ( i ) ] ⊤ \mathbf{x}^{(i)} [x_1^{(i)}, x_2…

【cdh】hive执行SQL提示缺少3.0.0-cdh6.3.2-mr-framework.tar.gz文件

问题&#xff1a;执行SQL报错提示缺少文件 异常信息如下 在hdfs上查看的时候连文件夹都没有&#xff0c;所以这个异常会抛出&#xff0c;但是我是基于CDH搭建的&#xff0c;可以直接基于下面操作 执行完成之后查看HDFS文件 重新执行SQL发现可以正常执行了

ad18学习笔记十六:v割

所谓“V割”是印刷电路板&#xff08;PCB&#xff09;厂商依据客户的图纸要求&#xff0c;事先在PCB的特定位置用转盘刀具切割好的一条条分割线&#xff0c;其目的是为了方便后续SMT电路板组装完成后的分板之用&#xff0c;因为其切割后的外型看起来就像个英文的“V”字型&…

【机器学习】强化学习(六)-DQN(Deep Q-Learning)训练月球着陆器示例

概述 Deep Q-Learning&#xff08;深度 Q 学习&#xff09;是一种强化学习算法&#xff0c;用于解决决策问题&#xff0c;其中代理&#xff08;agent&#xff09;通过学习在不同环境中采取行动来最大化累积奖励。Lunar Lander 是一个经典的强化学习问题&#xff0c;其中代理的任…

教学质量常态监控与评价平台

教学质量常态监控与评价平台&#xff0c;以提高教学质量为目标导向&#xff0c;利用Al、大数据等新型技术手段作为技术支撑&#xff0c;服务于教学质量科、督导、教师、学生等角色&#xff0c;基于教学过程数据&#xff0c;关注教师的教学内涵&#xff0c;覆盖了对教师、课堂、…