面试必问-mybatis框架之分页插件PageHelperd底层源代码分析

10 篇文章 0 订阅
7 篇文章 0 订阅

活动地址:毕业季·进击的技术er

职场人之内卷人

PageHelper的具体用法还需要我给大家讲吗?算了讲一下得了。用法非常之简单

导入依赖

<dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>XXXX</version>
</dependency>

在xml中的sql写法 ,很简单不用写 Limi ?,?的。举个栗子巴

<select id="findAll" resultType="com.xxx.xxx.dao.XxxxDao" >
    select *from sys_user
</select>

我们在service层 的写法

// pageNum 第几页
// pageSize 每页展示条数
public PageInfo<SysUser> page(Integer pageNum,
                          Integer pageSize){
    PageHelper.startPage(pageNum,pageSize);
    List<SysUser> list=SysUserMapper.findAll();
    PageInfo<SysUser> sysUserPage = new PageInfo<SysUser>(list);
    return sysUserPage;
}

用法的话也可以百度 关键词:PageHelper的用法。

首先我们说一下我们为什么要用 PageHelper

  1. 帮我们做分页
  2. 帮我们查询总条数

就聊聊怎么帮我们分页的

如果不懂 ThreadLocal 你就先看看 大概需要 5-10分分钟

先说结论

其实巴!PageHelperd 底层帮我们做了 LIMIT ?, ?
请求过来也就相当一个线程过来。要想实现线程之间隔离,每一个线程都有自己的副本。最佳选择 ThreadLocal
mybatis 中的拦截器

废话不多说上源码

PageHelper.startPage 点进去

在这里插入图片描述

最终调用的是: 看上去挺简单 就是 对象赋值
 /**
     * 开始分页
     *
     * @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 oldPage = getLocalPage();、setLocalPage(page);

来学习一下巴 这俩个方法

Page oldPage = getLocalPage(); 你要是学过 ThreadLocal 你一看便知 这里就不在奥术了

protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
public static <T> Page<T> getLocalPage() {
        return LOCAL_PAGE.get();
    }

setLocalPage(page); 先猜一下 别看结论 :往ThreadLocal 赋值 page的

protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
 public static <T> Page<T> getLocalPage() {
        return LOCAL_PAGE.get();
    }

以上的代码分析也是很重要的欧!!!哈哈哈

那个那个~~~ Mybatis拦截器 在PageHelperd源码中如何使用的

注解@Intercepts和接口Interceptor 联合使用 才会生效哈!
@Signature 注解中的属性 聊一下。type :要拦截的类,method:拦截类中的那个方法,args:要处理的 对象 作为参数

@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 {
}

执行代码 断点走起

在这里插入图片描述

代码执行结果说明:

在这里插入图片描述

算了 我把这个方法都粘贴出来

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

我就不一行一行的讲了 我就 断点到关键代码

  • 当前方法 断点放到 resultList = ExecutorUtil.pageQuery(dialect, executor,ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
    在这里插入图片描述

F7 进到那个Utils 中

代码也粘贴出来 你们呢那边方便看看
 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);
        }
    }
仔细看断点位置 兄弟们

在这里插入图片描述

实际数据分析

在这里插入图片描述

更进去

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

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

代码往下走

在这里插入图片描述

不懂mybatis的源码的可以康康我的这篇文章

二分钟带你走进MyBatis的执行流程与底层解析

求总条数的那个方法 我在这里就不讲了 思想都是一样饿 就是参数 不同。查询的方法还是一样的

看一下代码截图

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

 public static Long executeAutoCount(Dialect dialect, Executor executor, MappedStatement countMs,
                                        Object parameter, BoundSql boundSql,
                                        RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
        //创建 count 查询的缓存 key
        CacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);
        //调用方言获取 count sql
        String countSql = dialect.getCountSql(countMs, boundSql, parameter, rowBounds, countKey);
        //countKey.update(countSql);
        BoundSql countBoundSql = new BoundSql(countMs.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
        //当使用动态 SQL 时,可能会产生临时的参数,这些参数需要手动设置到新的 BoundSql 中
        for (String key : additionalParameters.keySet()) {
            countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
        }
        //对 boundSql 的拦截处理
        if (dialect instanceof BoundSqlInterceptor.Chain) {
            countBoundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.COUNT_SQL, countBoundSql, countKey);
        }
        //执行 count 查询
        Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
        Long count = (Long) ((List) countResultList).get(0);
        return count;
    }

这样的话还差最后一个地方没有讲到了 哈哈哈

dialect.afterAll();
在这里插入图片描述

dialect.afterAll(); 代码跟景区

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

分析一下源码

@Override
    public void afterAll() {
        //这个方法即使不分页也会被执行,所以要判断 null
        AbstractHelperDialect delegate = autoDialect.getDelegate();
        if (delegate != null) {
        // 空方法
            delegate.afterAll();
            // 移除代理对象的ThreadLocal
            autoDialect.clearDelegate();
        }
        // 移除 当前 page 对象的 ThreadLocal
        clearPage();
    }

职场人:SteveCode
编写实属不易,看到这里 就三联一手巴! 谢谢各位!
点赞,收藏,分享、关注。算了 ! 能帮到你 是我最大的心愿。
不足的地方 。别忘了指正哈!一块进步
本人公众号:SteveCode
技术分享的,关注我不吃亏。哈哈哈

活动地址:毕业季·进击的技术er

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SteveCode.

永远年轻,永远热泪盈眶

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值