PageHelper分页插件隐藏的坑


原文链接
RocketMQ思维导图,不看会后悔哟
Mysql思维导图分享

上面思维导图可去gongzhonghao回复:扣扣号,获取联系方式后找我免费获得可编辑版本。 后面会继续分享其他思维导图,包括Redis、JVM、并发编程、RocketMQ、RabbtiMQ、Kafka、spring、Zookeeper、Dubbo等等

一、问题

  2022年7月29日上午10点多的时候突然收到线上报警,问题有两个,一是大量数据库请求等待导致内存快满了,二是有sql执行报错。

  数据库等待的原因是因为运维的问题导致有些数据库不能访问,从而引发大量请求等待。所以就会有大量创建的对象不能被回收,从而导致内存满了。

  而sql报错是因为一个sql被莫名其妙的在最后面加上了limit ?,?。代码里的原sql(原sql较多,这里我只是写了个类似的sql)是 select * from station_letter order by id ;,真正执行的sql却是select * from station_letter order by id ; limit ?,?,很明显sql里的分号是不对的。

二、分析问题

  看到追加的 limit ?,?,很容易想到是分页拦截后加上去的。在我们项目里引入了PageHelper这个组件,用到它的地方代码如下:

PageHelper.startPage(request.getPageNum(), request.getPageSize());
List<StationLetterListVO> list = stationLetterMapper.letterList(request);

  但是奇怪的是,在调用报错sql的方法前面没有任何地方用到PageHelper.startPage(),这就意味着报错sql不应该会被拦截处理。

  可是结果就是被拦截处理了。

  由于“是否分页处理”的值是放在ThreadLocal里的,那么我猜测应该是一个线程设置了分页值,而后又被另外的线程也拿到了该值。

  我们知道ThreadLocal里的值是线程局部变量, 它的生命周期是和线程一样的。如果是新开的线程是不会访问到别的线程里的ThreadLocal里的值的。现在的情况是一个没有分页的请求,却拿到了有分页请求的值,那只能说明肯定两个请求都是在线程池里拿到的线程,一个线程未释放threadLocal里的值就放回线程池了,然后又被别的请求拿到了。

  既然真实结果的确是拦截处理了,那么我们就只能查看PageHelper拦截的源码了,去看看是否释放了,下面是我找到的源码:

try {
    // 处理分页,并执行查询
    Object result = this._processPage(invocation);
    var3 = result;
} finally {
    clearLocalPage(); // 释放资源
}

  从代码看到finally里有最终释放threadLocal里的分页值,照理说就不应该被其他线程拿到。这时我就猜想:一定是在调用PageHelper.startPage()之后,执行 clearLocalPage()之前,应该有地方抛异常出来,导致 clearLocalPage(); 不能执行,从而释放分页值失败。

  结合前面数据库连接不上的问题,就想到可能是因为设置分页值后,数据库的异常导致没有走释放的代码,于是走断点去查了代码执行顺序来验证。最终结果确实是猜想那样,梳理出的最终伪代码调用顺序如下:

1. 设置分页值:PageHelper.startPage(request.getPageNum(), request.getPageSize());
2. 获取数据库连接
3. 处理分页:
try {
    Object result = this._processPage(invocation);
    var3 = result;
} finally {
    4. 释放分页值
    clearLocalPage();
}

  因为数据库连不上,所以在第二步的时候就已经大量报错,导致不能执行后面的释放threadLocal值的代码。而在PageHelper里,只要threadLocal里有值,它就会将sql根据分页值进行改写,所以就会报错。

三、解决问题

  看到问题是不是就知道怎么解决了?这其实就是一个资源释放的问题,类似释放锁资源一样,需要保证资源一定能够释放,所以业务代码在finally里加SqlUtil.clearLocalPage();就好了。SqlUtil.clearLocalPage();是我根据clearLocalPage();从PageHelper源码里找到的释放方法,所以是没有问题的。最终代码如下:

PageHelper.startPage(request.getPageNum(), request.getPageSize());
try {
    List<StationLetterListVO> list = stationLetterMapper.letterList(request);
    // 其它业务代码
}finally {
    SqlUtil.clearLocalPage();
}

  释放资源操作,包括我们在释放锁时,不会出现漏掉释放操作的原则就是:在设置值和释放值之间不能有任何能够产生异常的代码,且释放资源要放在finally里保证一定会调用到

  后来听说PageHepler新版本已解决此问题,我们用的版本是4.1.6。官方解决办法也就是在PageHelper里加了手动释放的方法!PageHelper官方也有并发安全的分页使用方法,有兴趣的可以去看看。

  我个人是不爱用这个分页插件的。有两个原因,一是因为里面的原理不清楚,不敢乱用,代码要以安全为主;二是分页插件只能处理简单的查询,复杂的count(*)改写有问题,而且在不面向老板编程的时候,分页的地方本身就很少,而面向老板编程时,复杂的查询一堆一堆的,这个分页插件也用不了。其实自己写分页,也就多了一个count(*)查询,也不费什么事。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
PageHelper分页件是一个用于在项目中实现分页的工具。通过在项目的pom.xml文件中添加PageHelper的依赖jar包,我们可以在代码中使用PageHelper提供的方法来实现分页功能。相比以前手动设置分页参数的方式,使用PageHelper可以简化代码的编写,提高开发效率。 在使用PageHelper进行分页时,我们不需要传入分页参数如page和size,而是直接返回一个Page对象,该对象包含了分页的结果数据和相关的分页信息。通过调用Page对象的方法,我们可以获取当前页的数据、总页数、总记录数等信息。同时,PageHelper还提供了一些辅助方法,如设置分页起始页码、每页显示的数据量等,以满足不同需求的分页操作。 总之,PageHelper分页件为我们提供了一种简单、方便的方式来实现分页功能,使得我们可以更轻松地处理大量数据的分页显示需求。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [PageHelper分页件实现](https://blog.csdn.net/lesliesuai/article/details/117902319)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [PageHelper分页件的简单使用](https://blog.csdn.net/qq_42640067/article/details/111938795)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值