首页 > 其他分享 >PageHelper这次给我深深上了一课!

PageHelper这次给我深深上了一课!

时间:2024-01-09 22:06:02浏览次数:25  
标签:深深 dialect 分页 parameter PageHelper ms 一课 page rowBounds

最近项目中出现了一些奇怪的现象!!查询全部分类的下拉列表只能查出5条数据?

PageHelper这次给我深深上了一课!_sql


明明有十多个结果,怎么只能返回5个?

当管理员在后台界面重置用户的密码的时候,居然报错了?

报错信息:sql中update语句不认识 “Limit 5”

可想而知,我的sql被拼接了“limit”分页参数!!!

PageHelper是怎么做到上面的问题的?

我首先得讲解一下基本使用。

代码如下:

startPage()干啥了?

PageHelper这次给我深深上了一课!_分页_02

PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable)的参数分别是:

  • pageNum:页数
  • pageSize:每页数据量
  • orderBy:排序
  • reasonable:分页合理化,对于不合理的分页参数自动处理,比如传递pageNum是小于0,会默认设置为1.

连续点击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);
    // 1、获取本地分页
    Page<E> oldPage = getLocalPage();
    if (oldPage != null && oldPage.isOrderByOnly()) {
        page.setOrderBy(oldPage.getOrderBy());
    }
     // 2、设置本地分页
    setLocalPage(page);
    return page;
}

到达终点位置了,分别是:getLocalPage()setLocalPage(page),分别来看下:

getLocalPage()

进入方法:

PageHelper这次给我深深上了一课!_sql_03

看看常量LOCAL_PAGE是个什么路数?

PageHelper这次给我深深上了一课!_分页_04

当一个请求来的时候,会获取持有当前请求的线程的ThreadLocal,调用LOCAL_PAGE.get(),查看当前线程是否有未执行的分页配置。

setLocalPage(page)

设置线程的分页配置:

PageHelper这次给我深深上了一课!_pageHelper_05


经过前面的分析,我们发现,问题似乎就是这个ThreadLocal导致的。

是否在使用完之后没有进行清理?导致下一次此线程再次处理请求时,还在使用之前的配置?

mybatis使用pageHelper分析

前面提到过,通过PageHelper的startPage()方法进行page缓存的设置,当程序执行sql接口mapper的方法时,就会被拦截器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.skip(ms, parameter, rowBounds)

此处的skip方法进行设置分页参数,内部调用方法:

PageHelper这次给我深深上了一课!_pageHelper_06

继续跟踪getPage(),发现此方法的第一行就获取了ThreadLocal的值:

Page page = PageHelper.getLocalPage();
统计数量:dialect.beforeCount(ms, parameter, rowBounds)

我们都知道,分页需要获取记录总数,所以,这个拦截器会在分页前先进行count操作。

如果count为0,则直接返回,不进行分页:

PageHelper这次给我深深上了一课!_分页_07

afterPage其实是对分页结果的封装方法,即使不分页,也会执行,只不过返回空列表。

分页:ExecutorUtil.pageQuery

在处理完count方法后,就是真正的进行分页了:


此方法在执行分页之前,会判断是否执行分页,依据就是前面我们通过ThreadLocal的获取的page。

当然,不分页的查询,以及新增和更新不会走到这个方法当中。

非分页:executor.query

而是会走到下面的这个分支:

resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);

我们可以思考一下,如果ThreadLoad在使用后没有被清除,当执行非分页的方法时,那么就会将Limit拼接到sql后面。

为什么不分也得也会拼接?我们回头看下前面提到的dialect.skip(ms, parameter, rowBounds):

PageHelper这次给我深深上了一课!_缓存_08

如上所示,只要page被获取到了,那么这个sql,就会走前面提到的ExecutorUtil.pageQuery分页逻辑,最终导致出现不可预料的情况。

在intercept方法的最后,会在sql方法执行完成后,清理page缓存:

finally {
    if(dialect != null){
        dialect.afterAll();
    }
}

看看这个afterAll()方法:

@Override
public void afterAll() {
    //这个方法即使不分页也会被执行,所以要判断 null
    AbstractHelperDialect delegate = autoDialect.getDelegate();
    if (delegate != null) {
        delegate.afterAll();
        autoDialect.clearDelegate();
    }
    clearPage();
}

只关注 clearPage()

/**
 * 移除本地变量
 */
public static void clearPage() {
    LOCAL_PAGE.remove();
}

总结:

PageHelper这次给我深深上了一课!_sql_09

所以,在使用PageHelper进行分页时,执行sql的代码要紧跟startPage()方法。我们还需要手动调用clearPage()方法,在存在问题的方法之前。


最后说一句(求关注!!)

求一键三连:点赞、转发、在看。

搜索公众号:woniuxgg,在公众号中回复:笔记 获得蜗牛为你精心准备的实战语雀笔记
回复面试、开发手册、有超赞的粉丝福利。

标签:深深,dialect,分页,parameter,PageHelper,ms,一课,page,rowBounds
From: https://blog.51cto.com/u_16502039/9166019

相关文章

  • 英语一课一练一年级扩展阅读03the Little Mermaid-小美人鱼
    PDF格式公众号回复关键字:YYYKYLY03记忆树1Hello,everybody.I’mAriel,thelittlemermaid.翻译大家好.我是Ariel,小美人鱼简化记忆美人鱼句子结构1打招呼(Greeting):"Hello,everybody."是一个简短的问候语,使用"Hello"向大家问好,"everybody"是名词短语,作为"......
  • PageHelper使用案例
    1@Override2publicCommonResultqueryReportByCallCountInfos(ReportByCallVoreportByCallVo){3PageHelper.startPage(reportByCallVo.getPageNum(),reportByCallVo.getPageSize());4List<ReportByCallVo>list=sysUsageStati......
  • 英语一课一练一年级扩展阅读02Art Class and Drawings-艺术课和绘画
    PDF格式公众号回复关键字:YYYKYLY02记忆树1Itistimefortheartclass.翻译现在是艺术课的时间。简化记忆艺术课句子结构1"Itistimefor":这是一个固定句型,用来表示做某件事情的时间到了。"it"(它)是形式主语,真正主语是"timefor"(做某件事情的时间);"for"(对于)是介词......
  • Springboot下PageHelper分页不生效问题
    今天在做一个小项目,引入PageHelper时踩了一个坑,记录一下。解决方案参考:SpringBoot+MyBatis使用pagehelper分页插件及其注意事项(含解决分页不生效问题)环境:SpringBoot3.2.0JDK17Postgresql15PageHelper1.2.12依赖<dependency><groupId>com.github.pagehelper</......
  • Java第十一课_内部类,Object类,枚举和异常
    1.内部类一般内部类publicclassPratice{publicstaticvoidmain(String[]args){/*内部类:描述事物内部的事物;就是一个类定义在另一个类的内部当内部类定义在成员变量的位置上时,可以被成员修饰符修饰,修饰后会具备修饰......
  • 共情营第一课
     一、情绪的管理 1、识别情绪;2、觉察情绪3、自我调整、转化二、共情式的沟通正确的沟通模式:1、把话说到对方心理去,能照顾到3件事:当下的情景、对方的情绪,也能让自己比较舒服;2、不断地训练清晰管理能力、疗愈自己,找到适合自己的方法,去共情式的沟通;错误的沟通模式:1、......
  • 前端歌谣-第五拾一课-node之http模块之stream流
    前言我是歌谣微信公众号关注前端小歌谣一起学习前端知识今天继续给大家讲解node中stream模块的讲解案例constfs=require("fs")constrs=fs.createReadStream("./1.txt","utf-8")rs.on("data",(chunk)=>{console.log(chunk)})rs.on("end",()=>{......
  • C++第一课
    之前不太会C++,在leetcode上尝试用C++解决算法问题这里我想使用CLion调试我的C++程序那么问题产生,我该如何创建我的第一个C++项目呢?step1.打开我的CLionso,那么现在我想知道C++Executable和C++Library的区别根据我的编程经验应该不选择C++LibraryC++Executab......
  • HarmonyOS第一课,配置DevEcoStudio,运行"哈喽word"
    1下载DevEcoStudio工具下载地址根据自己电脑的os和芯片版本,下载对应的安装包,顺便也把其他2个开发者工具也下载下来了2运行DevEcoStudio,并配置相关环境变量如果自检有不满足的环境配置,可以在线安装至指定文件夹,强迫症请准备好指定路径存放npm及ohpm安装路径安装HarmonyOS-Sd......
  • 【全栈第一课】Vue快速入门
    一、前端工程化JS的复用就是模块化UI样式复用就是组件化(LayUI里面的都是组件化)所以说前端工程化就是有规范的写,不能你一个样式我一个样式,不够统一不够专业!二、WebPack是什么前端工程化的具体实现方案基本使用实现奇偶行变色1.初始化包管理工具通过npminit-y生成2.安装jquery......