PageHelper分页原理(源码)

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分页位置

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

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

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值