PageHelper 使用 ThreadLocal 的线程复用问题,你用对了吗?

简介: PageHelper 使用 ThreadLocal 的线程复用问题,你用对了吗?
  • 前言
  • 问题场景
  • 解决方案

image.png

前言

PageHelper 是较为常用的分页插件,通过实现 Mybatis 的 Interceptor 接口完成对 query sql 的动态分页,其中分页参数由 ThreadLocal 进行保存。

简单的 分页执行过程:

  1. 设置 page 参数
  2. 执行 query 方法
  3. Interceptor 接口 中校验 ThreadLocal 中是否存在有设置的 page 参数
  4. 存在 page 参数,重新生成 count sqlpage sql,并执行查询。不存在 page 参数,直接返回 查询结果
  5. 执行 LOCAL_PAGE.remove()清除 page 参数

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能。

项目地址:https://github.com/YunaiV/ruoyi-vue-pro

问题场景

观察上述的执行过程,可以发现,如果在第 1 步和第 2 步 之间发生异常,那么 LOCAL_PAGE 中当前线程对应的 page 参数并不会 remove。

在不使用线程池的情况下,当前线程在执行完毕后会被销毁,这时 当前线程 中的 threadLocals 参数 将会被情况,也就清空 了 LOCAL_PAGE 中 当前线程的 page 参数。

但是如果使用了线程池,当前线程执行完毕,并不会被销毁,而是会将当前线程再次存放到池中,标记为空闲状态,以便后续使用。在后续使用这个线程的时候,由于 线程 的 threadLocals 依旧存在有值,尽管我们在第 1 步时未设置 page 参数,第 3 步 的也能获取到page参数,从而生成 count sqlpage sql,从而影响我们的正常查询。

SpringBoot 项目中会使用内置的 Tomcat 作为服务器,而Tomcat会默认使用线程来处理请求,从而便引发了上述问题。

基于微服务的思想,构建在 B2C 电商场景下的项目实战。核心技术栈,是 Spring Boot + Dubbo 。未来,会重构成 Spring Cloud Alibaba 。

项目地址:https://github.com/YunaiV/onemall

解决方案

因为Tomcat的线程是用来处理request请求,那么在请求完成时,清空当前线程的threadLocals 属性值,也就是执行 LOCAL_PAGE.remove() 即可。实现方式:

  • 使用 aop,对所有 controller 进行处理
  • 实现 HandlerInterceptor 或者 WebRequestInterceptor 对 request 请求的拦截器接口,通过 afterCompletion 方法执行 LOCAL_PAGE.remove()。— 推荐,实现方式简单

这里使用第二种方式,实现 HandlerInterceptor 接口:

public class PageLocalWebInterceptor implements HandlerInterceptor {
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // PageHelper.clearPage() 内部调用 LOCAL_PAGE.remove()
        PageHelper.clearPage();
    }
}

定义配置类,配置类需实现 WebMvcConfigurer 接口完成对于WebMvc的相关配置 ,注册 PageLocalWebInterceptor

@Configuration
public class FrameworkAutoConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new PageLocalWebInterceptor());
    }
}
相关文章
|
1月前
|
存储 Java 测试技术
ThreadLocal:线程专属的变量
ThreadLocal:线程专属的变量
42 0
|
3月前
|
存储 Java 数据安全/隐私保护
【JUC】ThreadLocal 如何实现数据的线程隔离?
【1月更文挑战第15天】【JUC】ThreadLocal 如何实现数据的线程隔离?ThreadLocal 导致内存泄漏问题?
|
3月前
|
存储 Java 数据安全/隐私保护
探索Java中神奇的ThreadLocal:为什么它是多线程编程的重要工具?
探索Java中神奇的ThreadLocal:为什么它是多线程编程的重要工具?
|
30天前
|
安全 Java
java中线程经常被问到ThreadLocal你懂吗?
java中线程经常被问到ThreadLocal你懂吗?
7 0
|
1月前
|
Java 关系型数据库 MySQL
【数据库连接,线程,ThreadLocal三者之间的关系】
【数据库连接,线程,ThreadLocal三者之间的关系】
21 0
|
1月前
|
存储 安全 Java
调用链跨线程传递 ThreadLocal 对象对比
说起本地线程专属变量,大家首先会想到的是 JDK 默认提供的 ThreadLocal,用来存储在整个链路中都需要访问的数据,并且是线程安全的。由于在落地全链路压测的过程中,一个基本并核心的功能需求是流量标记需要在整个链路中进行传递,那么线程上下文环境成为解决这个问题最合适的技术。
43 2
调用链跨线程传递 ThreadLocal 对象对比
|
1月前
|
存储 安全 Java
多线程------ThreadLocal详解
多线程------ThreadLocal详解
|
2月前
|
存储 安全 Python
什么是Python中的线程局部存储(Thread Local Storage)?
【2月更文挑战第3天】【2月更文挑战第6篇】
|
4月前
|
存储 安全 Java
【线程本地变量ThreadLocal】—— 每天一点小知识
【线程本地变量ThreadLocal】—— 每天一点小知识
|
5月前
|
安全 Java 数据库连接
ThreadLocal Java多线程下的影分身之术
后来,我把c删掉,变成了下面这样。如果我现在想查h,按照上面getEntry的逻辑,是不是遍历到3就停了,所以找不到h了? getEntry的逻辑表面确实是这样,但实际上getEntryAfterMiss、remove、gets时都会直接或者间接调用expungeStaleEntry会对表里的数据做整理。expungeStaleEntry()除了利用弱引用的特性对tab中Entry做清理外,还会对之前Hash冲突导致后移的Entry重新安放位置。所以不可能出现下面这种tab排放的。
22 0