PageMethod源码
public abstract class PageMethod {
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal();
protected static boolean DEFAULT_COUNT = true;
public PageMethod() {
}
protected static void setLocalPage(Page page) {
LOCAL_PAGE.set(page);
}
public static <T> Page<T> getLocalPage() {
return (Page)LOCAL_PAGE.get();
}
public static void clearPage() {
LOCAL_PAGE.remove();
}
}
PageHelper 是较为常用的分页插件,通过实现 Mybatis 的 Interceptor 接口完成对 query sql 的动态分页。
从PageMethod中可以看出,分页参数由 ThreadLocal 进行保存。
大致分为下面几步
- 设置 page 参数
- 执行 query 方法
- Interceptor 接口中校验 ThreadLocal 中是否存在有设置的 page 参数
- 存在 page 参数,重新生成 count sql 和 page sql,并执行查询。不存在 page 参数,直接返回 查询结果
- 执行 LOCAL_PAGE.remove() 清除 page 参数
问题场景
PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
// 可以换成其它业务逻辑
int i = 1 / 0;
List<User> dataList = this.baseMapper.getList();
观察上述的执行过程,可以发现,如果在第 1 步和第 2 步 之间发生异常,那么 LOCAL_PAGE 中当前线程对应的 page 参数并不会 remove。
如果不适用线程池那还好,线程在执行完毕后会被销毁。
用了线程池,当前线程执行完毕,并不会被销毁,会将当前线程存放到池中,标记为空闲状态,以便后续使用。
后续使用未被清空参数的线程时。
由于线程 的 threadLocals 依旧存在有值,即使没有设置page参数,线程中仍然有参数,从而生成 count sql 和 page sql有问题!!!
从而影响我们的正常查询。
解决方案
1. 贴紧【第一个查询有效】
PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
List<User> dataList = this.baseMapper.getList();
2. try–catch–finally
try {
PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
List<User> dataList = this.baseMapper.getList();
} finally {
PageHelper.clearPage();
}
3. 使用前先清空
protected void startPage(int pageNum, int pageSize) {
PageHelper.clearPage();
PageHelper.startPage(pageNum, pageSize);
}
4. request请求
请求完成时,清空当前线程的threadLocals 属性值,也就是执行 LOCAL_PAGE.remove() 即可。
实现方式:
使用 aop,对所有 controller 进行处理
实现 HandlerInterceptor 或者 WebRequestInterceptor 对 request 请求的拦截器接口,通过 afterCompletion 方法执行 LOCAL_PAGE.remove() 。
//Interceptor
public class PageWebInterceptor implements HandlerInterceptor {
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// PageHelper.clearPage() 内部调用 LOCAL_PAGE.remove()
PageHelper.clearPage();
}
}
//配置类自动生效
@Configuration
public class PageWebAutoConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new PageWebInterceptor());
}
}