PageHelper使用分页错乱问题

260 阅读2分钟

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 进行保存。

大致分为下面几步

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

问题场景

PageHelper.startPage(dto.getPageNum(), dto.getPageSize());
// 可以换成其它业务逻辑
int i = 1 / 0;
List 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());
   }
}