PageHelper分页原理(源码)

2023-11-01

PageHelper分页原理

PageHelper是我们经常使用的一个分页插件,之前咱们一直处于使用阶段的,今天咱们去探究一下其中的原理。
SQL语句实现分页查询知识,就不在赘述了。

LIMIT i,a;
 i:是指查询的索引值(默认是0)
 a:是指查询的数量值
 
 SELECT id FROM '表' WHERE '条件' LIMIT (i-1)*a,a; 

首先,咱们使用PageHelper分页首先要在查询数据库数据方法前先调用PageHelper.startPage方法。

pageNum 当前页数
pageSize  每页有多少条数据
orderBy    数据的排序条件
PageHelper.startPage(pageNum, pageSize, orderBy);

该方法的三个参数都是前端传递过来的,咱们debug进入这个方法一探究竟,发现这个方法根据,pageNum、pageSize创建了一个page类,然后给他set了一个排序的条件,至此创建出一个page分页类,在这个方法之后调用的查询数据库方法都会进行分页查询。

public static <E> Page<E> startPage(int pageNum, int pageSize, String orderBy) {
    Page<E> page = startPage(pageNum, pageSize);
    page.setOrderBy(orderBy);
    return page;
}

咱们继续来看为啥会实现分页的,直接debug进入startPage方法下的查询数据库的方法。进入了一个cglib代理的分页拦截器,咱们这个不需要aop代理之类的操作直接调用方法就可以。

@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    Object target = null;
    TargetSource targetSource = this.advised.getTargetSource();

    Object var16;
    try {
        if (this.advised.exposeProxy) {
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }

        target = targetSource.getTarget();
        Class<?> targetClass = target != null ? target.getClass() : null;
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = methodProxy.invoke(target, argsToUse);
        } else {
            retVal = (new CglibAopProxy.CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)).proceed();
        }

        retVal = CglibAopProxy.processReturnType(proxy, target, method, retVal);
        var16 = retVal;
    } finally {
        if (target != null && !targetSource.isStatic()) {
            targetSource.releaseTarget(target);
        }

        if (setProxyContext) {
            AopContext.setCurrentProxy(oldProxy);
        }

    }

    return var16;
}

直接执行这个方法 retVal = methodProxy.invoke(target, argsToUse);调用原方法,现在还和分页原理无关,请看官耐心等待

public Object invoke(Object obj, Object[] args) throws Throwable {
    try {
        this.init();
        MethodProxy.FastClassInfo fci = this.fastClassInfo;
        return fci.f1.invoke(fci.i1, obj, args);
    } catch (InvocationTargetException var4) {
        throw var4.getTargetException();
    } catch (IllegalArgumentException var5) {
        if (this.fastClassInfo.i1 < 0) {
            throw new IllegalArgumentException("Protected method: " + this.sig1);
        } else {
            throw var5;
        }
    }
}

return fci.f1.invoke(fci.i1, obj, args);执行原方法

/**
 * 根据条件分页查询字典类型
 * 
 * @param dictType 字典类型信息
 * @return 字典类型集合信息
 */
@Override
public List<SysDictType> selectDictTypeList(SysDictType dictType)
{
    return dictTypeMapper.selectDictTypeList(dictType);
}

来到这个方法,method.getDeclaringClass()判断这个类是否是object基础类型的类是的话直接调基础类型,不是的话调用this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession)

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
    } catch (Throwable var5) {
        throw ExceptionUtil.unwrapThrowable(var5);
    }
}

来到这个方法sqlSession和 args继续调用 ,sqlSession是操作数据库的类

public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    return this.mapperMethod.execute(sqlSession, args);
}

进入这个方法,因为是 SELECT 所以执行result = this.executeForMany(sqlSession, args);

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    Object param;
    switch(this.command.getType()) {
    case INSERT:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
        break;
    case UPDATE:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
        break;
    case DELETE:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
        break;
    case SELECT:
        if (this.method.returnsVoid() && this.method.hasResultHandler()) {
            this.executeWithResultHandler(sqlSession, args);
            result = null;
        } else if (this.method.returnsMany()) {
            result = this.executeForMany(sqlSession, args);
        } else if (this.method.returnsMap()) {
            result = this.executeForMap(sqlSession, args);
        } else if (this.method.returnsCursor()) {
            result = this.executeForCursor(sqlSession, args);
        } else {
            param = this.method.convertArgsToSqlCommandParam(args);
            result = sqlSession.selectOne(this.command.getName(), param);
            if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                result = Optional.ofNullable(result);
            }
        }
        break;
    case FLUSH:
        result = sqlSession.flushStatements();
        break;
    default:
        throw new BindingException("Unknown execution method for: " + this.command.getName());
    }

    if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
        throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
    } else {
        return result;
    }
}

来到executeForMany方法

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    Object param = this.method.convertArgsToSqlCommandParam(args);
    List result;
    if (this.method.hasRowBounds()) {
        RowBounds rowBounds = this.method.extractRowBounds(args);
        result = sqlSession.selectList(this.command.getName(), param, rowBounds);
    } else {
        result = sqlSession.selectList(this.command.getName(), param);
    }

    if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
        return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
    } else {
        return result;
    }
}

执行result = sqlSession.selectList(this.command.getName(), param);

public <E> List<E> selectList(String statement, Object parameter) {
    return this.sqlSessionProxy.selectList(statement, parameter);
}

又invoke晕了,可能因为是selectList方法是代理出来的
在这里插入图片描述

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

    Object unwrapped;
    try {
        Object result = method.invoke(sqlSession, args);
        if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
            sqlSession.commit(true);
        }

        unwrapped = result;
    } catch (Throwable var11) {
        unwrapped = ExceptionUtil.unwrapThrowable(var11);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
            SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            sqlSession = null;
            Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
            if (translated != null) {
                unwrapped = translated;
            }
        }

        throw (Throwable)unwrapped;
    } finally {
        if (sqlSession != null) {
            SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }

    }

    return unwrapped;
}

Object result = method.invoke(sqlSession, args);调用原方法DefaultSqlSession 中selectList方法

public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

执行this.selectList(statement, parameter, RowBounds.DEFAULT);

private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    List var6;
    try {
        MappedStatement ms = this.configuration.getMappedStatement(statement);
        var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
    } catch (Exception var10) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var10, var10);
    } finally {
        ErrorContext.instance().reset();
    }

    return var6;
}

这个方法貌似没作用

private Object wrapCollection(Object object) {
    return ParamNameResolver.wrapToMapIfCollection(object, (String)null);
}

调用var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler); 如果然代理的方法都会进入invoke ,这个类是个InvocationHandler
在这里插入图片描述

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
        return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
    } catch (Exception var5) {
        throw ExceptionUtil.unwrapThrowable(var5);
    }
}

绕来绕去,来到关键点了激动 pageintercepted类中的拦截器!!!

public Object intercept(Invocation invocation) throws Throwable {
    Object var16;
    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) {
            boundSql = ms.getBoundSql(parameter);
            cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
        } else {
            cacheKey = (CacheKey)args[4];
            boundSql = (BoundSql)args[5];
        }

        this.checkDialectExists();
        if (this.dialect instanceof Chain) {
            boundSql = ((Chain)this.dialect).doBoundSql(Type.ORIGINAL, boundSql, cacheKey);
        }

        List resultList;
        if (!this.dialect.skip(ms, parameter, rowBounds)) {
            if (this.dialect.beforeCount(ms, parameter, rowBounds)) {
                Long count = this.count(executor, ms, parameter, rowBounds, (ResultHandler)null, boundSql);
                if (!this.dialect.afterCount(count, parameter, rowBounds)) {
                    Object var12 = this.dialect.afterPage(new ArrayList(), parameter, rowBounds);
                    return var12;
                }
            }

            resultList = ExecutorUtil.pageQuery(this.dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
        } else {
            resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
        }

        var16 = this.dialect.afterPage(resultList, parameter, rowBounds);
    } finally {
        if (this.dialect != null) {
            this.dialect.afterAll();
        }

    }

    return var16;
}

resultList = ExecutorUtil.pageQuery(this.dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);获得最后结果数据

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)) {
        return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
    } else {
        parameter = dialect.processParameterObject(ms, parameter, boundSql, cacheKey);
        String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey);
        BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);
        Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
        Iterator var12 = additionalParameters.keySet().iterator();

        while(var12.hasNext()) {
            String key = (String)var12.next();
            pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
        }

        if (dialect instanceof Chain) {
            pageBoundSql = ((Chain)dialect).doBoundSql(Type.PAGE_SQL, pageBoundSql, cacheKey);
        }

        return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, pageBoundSql);
    }
}

执行String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey);

public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
    return this.autoDialect.getDelegate().getPageSql(ms, boundSql, parameterObject, rowBounds, pageKey);
}

获取分页的sql语句,Page page = this.getLocalPage();获取存储到本地线程的 page 类

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

    return page.isOrderByOnly() ? sql : this.getPageSql(sql, page, pageKey);
}
getLocalPage在本地获取page
public static <T> Page<T> getLocalPage() {
    return (Page)LOCAL_PAGE.get();
}

进入converToOrderBySql,可以看到給普通的sql语句中拼接了一个" order by "排序的条件,完成了排序的需求

public static String converToOrderBySql(String sql, String orderBy) {
    Statement stmt = null;

    try {
        stmt = CCJSqlParserUtil.parse(sql);
        Select select = (Select)stmt;
        SelectBody selectBody = select.getSelectBody();
        List<OrderByElement> orderByElements = extraOrderBy(selectBody);
        String defaultOrderBy = PlainSelect.orderByToString(orderByElements);
        if (defaultOrderBy.indexOf(63) != -1) {
            throw new PageException("原SQL[" + sql + "]中的order by包含参数,因此不能使用OrderBy插件进行修改!");
        }

        sql = select.toString();
    } catch (Throwable var7) {
        log.warn("处理排序失败: " + var7 + ",降级为直接拼接 order by 参数");
    }

    return sql + " order by " + orderBy;
}

return page.isOrderByOnly() ? sql : this.getPageSql(sql, page, pageKey);判断是否只是orderby排序如果需要分页进入getPageSql方法,拼接 LIMIT语句
1.如果是第一页拼接LIMIT ?
2.不是的话拼接LIMIT ?, ?

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

        return sqlBuilder.toString();
    }
}

至此分页的基本功能就完成了拼接完成了一个标准的分页sql语句

SELECT dict_id, dict_name, dict_type, status, create_by, create_time, remark FROM sys_dict_type order by dict_id asc
 LIMIT ?, ? 

分页源码代码的运行步骤
在这里插入图片描述
总结:pageHpler原理其实还简单,就是有一个PageInterceptor拦截器中拦截了原生的sql语句,拼接对应的排序orderby条件和limit分页位置

如果没有批评,赞美将毫无意义,欢迎指正批评。

路漫漫其修远兮,吾将上下而求索

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

PageHelper分页原理(源码) 的相关文章

随机推荐

  • Qt 插件创建教程

    Qt 插件创建教程 Qt 是一款非常流行的跨平台GUI应用程序开发工具 它提供了丰富的API和工具库 让开发者快速开发出高质量的应用程序 其中 在Qt中 插件是一个非常重要的概念 它可以帮助我们实现模块化编程 可以让我们的应用程序更加灵活
  • Linux内核学习笔记(八)Page Cache与Page回写

    你也可以通过我的独立博客 www huliujia com 获取本篇文章 综述 Page cache是通过将磁盘中的数据缓存到内存中 从而减少磁盘I O操作 从而提高性能 此外 还要确保在page cache中的数据更改时能够被同步到磁盘上
  • 数值分析实验(二)迭代法的应用

    目录 实验名称 数值分析实验 二 迭代法的应用 实验题目 实验原理 1 高斯消去法 2 Jacobi迭代法 3 G S迭代法 4 SOR迭代法 实验数据记录及处理 实验内容及步骤 1 高斯消元法 2 Jacobi迭代法 3 G S迭代法 4
  • 如何使用Python进行桌面应用开发?

    Python提供了多个库和框架来进行桌面应用开发 以下是使用Python进行桌面应用开发的常用方法之一 PyQt PyQt是一个用于开发跨平台桌面应用的Python库 它提供了丰富的GUI组件和工具 以下是使用PyQt创建桌面应用的基本步骤
  • Linux上启用kvm嵌套虚拟化功能

    kvm支持嵌套虚拟化 即可以在虚拟机中创建虚拟机 本文主要介绍如何在使用Intel处理器的CentOS7中开启KVM的嵌套虚拟化功能 kvm主要是通过内核模块来实现的 因此我们查看系统是否开启了kvm嵌套虚拟化 只需要 cat sys mo
  • 代码随想录算法训练营 个人总结

    训练营周期 2023 5 10 7 8 共计60天 LeetCode记录 参加训练营之前 就有想刷LeetCode的想法 一方便没有头绪地不知道按什么顺序刷题 另一方面也没有找到很好的讲解材料 都是自己看LeetCode页面上讨论模块下的高
  • Spark SQL数据源 - 基本操作

    目录 一 基本操作 二 默认数据源 一 默认数据源Parquet 二 案例演示读取Parquet文件 1 在Spark Shell中演示 2 通过Scala程序演示 一 基本操作 Spark SQL提供了两个常用的加载数据和写入数据的方法
  • git上创建自己的仓库并将新项目推上去

    1 在github上创建仓库 创建后复制地址 2 先进入项目文件夹 通过命令 git init 把这个目录变成git可以管理的仓库 git init 3 把文件添加到版本库中 使用命令 git add 添加到暂存区里面去 不要忘记后面的小数
  • 【华为OD机试真题2023B卷 JAVA&JS】找单词

    华为OD2023 B卷 机试题库全覆盖 刷题指南点这里 找单词 时间限制 1秒 内存限制 32768K 语言限制 不限 题目描述 给一个字符串和一个二维字符数组 如果该字符串存在于该数组中 则按字符串的字符顺序输出字符串每个字符所在单元格的
  • matlab newff函数弃用了,MATLAB神经网络函数NEWFF()新旧用法差异

    摘要 在Matlab R2010a版中 如果要创建一个具有两个隐含层 且神经元数分别为5 3的前向BP网络 使用旧的语法可以这样写 net1 newff minmax P 5 3 1 注意minmax 函数的使用 还有对输出层神经元数 1
  • 论文笔记:用于动态薄膜干涉测量的高光谱成像《Hyperspectral imaging for dynamic thin film interferometry》

    论文地址 Hyperspectral imaging for dynamic thin film interferometry Scientific Reports 目录 论文简介 阻因 现有方法及缺陷 文章贡献 理论依据 实验 实验结果与
  • php curlopt_header,php curl中CURLOPT_HTTPHEADER 这个参数的含义

    php curl中CURLOPT HTTPHEADER 这个参数的含义是 CURLOPT HTTPHEADER 一个用来设置HTTP头字段的数组 Content Type 表示后面的文档属于什么MIME类型 charset表示浏览器可接受的
  • Unity之使UI显示在最上层

    一 原理 Camera Depth 摄像机深度 深度值越小 越浅 越浮于表面 越近 前 Sorting Layer 排序层级 Order In Layer 在排序层中的层级 Render Queue 在Shader中对Tags的Queue进
  • 哗啦啦收银系统配置

    步骤 一 安装哗啦啦收银系统二 安装小票驱动三 创建店铺 进入收银系统 详情请参考我的个人博客 哗啦啦系统后台新增店铺 四 修改打印机模板五 修改打印机名称 准备 小票驱动 安装包 打印模板 功能包 一 安装哗啦啦收银系统 二 安装小票驱动
  • Python IDLE的下载,安装与使用教程

    本次是windows下的IDLE Linux IDLE是没有的 可以直接使用相应的Python 解释器 首先我们现在先进入Python的官网 https www python org 去下载一个Python IDLE程序安装包 找到Down
  • JDBC编码六步走

    JDBC介绍 Java Database Connectivity 简称JDBC 是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口 JDBC的本质 JDBC是SUN公司制定的一套连接数据库的接口 interface 而接口都
  • 关于 HTTP GET/POST 请求参数长度最大值的一个理解误区

    1 Get方法长度限制 Http Get方法提交的数据大小长度并没有限制 HTTP协议规范没有对URL长度进行限制 这个限制是特定的浏览器及服务器对它的限制 如 IE对URL长度的限制是2083字节 2K 35 下面就是对各种浏览器和服务器
  • numpy+matplotlib画正余弦函数图像

    coding utf 8 import matplotlib pyplot as plt import numpy as np import math x np arange 0 10 0 1 print x size y1 np zero
  • chatGPT的优点

    ChatGPT 的优点有以下几点 语言能力强 ChatGPT 是一个大型的语言模型 它能够理解和生成多种语言 具有很强的语言处理能力 记忆力强 ChatGPT 被训练过大量的文本数据 因此它对文本的记忆力也很强 能够快速回答用户的问题 智能
  • PageHelper分页原理(源码)

    PageHelper分页原理 PageHelper是我们经常使用的一个分页插件 之前咱们一直处于使用阶段的 今天咱们去探究一下其中的原理 SQL语句实现分页查询知识 就不在赘述了 LIMIT i a i 是指查询的索引值 默认是0 a 是指