🍰 个人主页:不摆烂的小劉
🍞文章有不合理的地方请各位大佬指正。
🍉文章不定期持续更新,如果我的文章对你有帮助➡️ 关注🙏🏻 点赞👍 收藏⭐️
在上一篇文章PageHelper 分页查询【底层代码-图文分析】【原理篇】探讨了PageHelper
的底层原理——拦截MyBatis
的查询操作,在Sql
语句执行前动态地添加分页逻辑,从而实现数据的分页查询。
本篇文章研究一下Pagehelper
常出现的问题。
文章目录
一、分页参数失效
1. 数据开启分页后查询的集合进行增删
!!!错误代码:
// 开启分页
PageHelper.startPage(1, 10);
// 执行查询
List<Product> products = productDAO.listProducts();
// 在查询结果集上进行增删操作
products.add(newProduct); // 添加新产品
products.remove(newProduct); // 删除产品
新增、删除新的元素,这只是在内存中的List
层面进行了操作,并没有改变数据库中的数据分布和分页相关的设置,但会导致当前页数据量不等于PageSize
2. 更新集合生成一个新集合。
分页查询的集合通过lambda
得到新的集合
!!!错误代码
PageMethod.startPage(dto.getPageNum(), dto.getPageSize());
List<ProductVO> products = productDAO.listProducts(dto);
// stream处理分页结果集
List<ProductVO> resultList = products.stream().map(one -> handleProduct(one)).collect(Collectors.toList());
return new PageInfo<>(resultList);
处理后的流收集到一个新的列表中,新的集合resultList
并不是 Page
类或其子类的实例,在new PageInfo<>(resultList)
中无法设置分页参数(为什么是Page
类或其子类的实例才能设置,请看上一篇(原理篇)的第二章第2、3节)
- 错误代码得到的结果:
- 对比一下正确结果:
解决
更新集合问题解决:
PageInfo
对象集合对象替换成新集合对象
PageMethod.startPage(dto.getPageNum(), dto.getPageSize());
List<ProductVO> products = productDAO.listProducts(dto);
// stream处理分页结果集,处理后的resultList 集合大小要与原集合products 大小相同
List<ProductVO> resultList = products.stream().map(one -> handleProduct(one)).collect(Collectors.toList());
PageInfo<ProductVO> pageProducts = new PageInfo<>(products);
pageProducts.setList(resultList);
return pageProducts
二、多limit的情况
ThreadLocal
与线程复用导致的 “污染”
Springboot
项目中内部使用线程池来处理请求。- 当一个线程完成一个请求的分页查询后,它的
ThreadLocal
中仍然保存着分页参数。 - 如果这个线程被线程池复用,去处理查询请求,会导致下一个查询也被添加了分页相关的
limit
语句
举例说明:线程数为1的线程池,两个查询请求
- 请求从
#1
处进入 - 执行到
#2
,将分页参数存储到当前线程的ThreadLocal
中,后面未跟查询 - 随后的请求从
#3
处进入 - 线程复用,执行
#4
处时执行的Sql
会被自动添加limit ?,?
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
1, // 核心线程数为1
1, // 最大线程数为1
1L, // 线程存活时间1分钟
TimeUnit.MINUTES,
new LinkedBlockingQueue<>(5) // 等待队列大小为5
);
public void listProducts() { //#1
threadPool.execute(() -> {
PageHelper.startPage(1, 10); //#2
});
}
public List<ProductVO> listCitys() { //#3
threadPool.execute(() -> {
List<City> list = cityDAO.lisCitys(); //#4
return list;
});
}
Pagehelper
在分页查询后会自动清除ThreadLocal
的分页参数。
设置分页参数,但后面未查询,方法直接返回。下次复用线程时,存在线程中的分页参数可能被其他查询消费。
解决
PageMethod.startPage
后紧跟查询
PageHelper.startPage(1, 10);
List<Country> list = countryMapper.selectIf(1);
或者lambda
用法
//jdk8 lambda用法
Page<Country> page = PageHelper.startPage(1, 10).doSelectPage(()-> countryMapper.selectGroupBy());
- 使用
PageHelper.clearPage()
手动清理
PageMethod.startPage(dto.getPageNum(), dto.getPageSize());
List<ProductVO> products = new ArrayList<>();
switch (dto.getBrand()) {
case "Huawei":
if ("Huawei".equals(brandToQuery)) {
products= productDAO.listProducts(dto);
}
break;
case "Apple":
if ("Apple".equals(brandToQuery)) {
products = productDAO.listProducts(dto);
}
break;
default:
//手动清理
PageHelper.clearPage()
break;
}
下一篇文章分析
ThreadLocal
原理和易错点
三、查询全部数据
分页查询失效,查询了全部数据
1. PageHelper.startPage(1,0)
当Pagesize<=0
时,不分页,也不会在Sql
末尾拼接limit
2. 错误配置reasonable
设置reasonable=true
时,若用户传入页数大于总页数,会修改查询页数,查询最后一页数据;当禁用此参数或设为false
,若用户传入页数大于总页数,则返回为空。
3. 没有配置拦截器插件
配置拦截器插件参考:如何使用分页插件
四、其他注意事项
除了上述易错问题,还有其他的注意事项
-
紧跟在
PageHelper.startPage
方法后的第一个Mybatis
的查询(Select)
方法会被分页。 -
不要在系统中配置多个分页插件,使用
Spring
时,选择其中一种:mybatis-config.xml
和Spring<bean>
配置方式 -
分页插件不支持带有
for update
语句的分页 -
分页插件不支持嵌套结果映射。嵌套结果方式会导致结果集被折叠,因此分页查询的结果在折叠后总数会减少,所以无法保证分页结果数量正确。
参考:
1.Pagehelper官方文档
2.Mybatis PageHelper分页遇到的坑,莫名其妙的增加了limit ?,?】
3.PageHelper失效问题
4.【PageHelper】PageHelper分页失效问题排查
5.浅析PageHelper踩坑:不安全分页导致的问题
6.PageHelper 分页插件使用中的那些“坑”
🍉文章不定期持续更新,如果我的文章对你有帮助➡️ 关注🙏🏻 点赞👍👍👍 收藏⭐️