1.问题描述以及业务代码展示
2.源码角度分析分页失效原因分析以及处理方案
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</exclusion>
</exclusions>
</dependency>
TestPageHelper .java
:
@RestController
@RequestMapping("/testPageHelper")
public class TestPageHelper {
@Autowired
private PageHelperServiceImpl pageHelperService;
@GetMapping
public ResultVo<PageInfo<News>> findNews(Integer currentPage,Integer pageSize){
PageHelper.startPage(currentPage,pageSize);
List<News> newsList = pageHelperService.findNews();
PageInfo<News> newsPageInfo = new PageInfo<News>(newsList);
newsPageInfo.setList(newsList);
return ResultVoUtil.success(newsPageInfo);
}
}
PageHelperServiceImpl.java
:
@Service
public class PageHelperServiceImpl {
@Autowired
private NewsMapper newsMapper;
// 查询资讯信息
public List<News> findNews(){
// 查询点赞信息
List<FindLike> like = newsMapper.findLike();
// 查询资讯信息
List<News> news = newsMapper.findNews();
return news;
}
}
mapper查询逻辑不进行展示,仅用作说明问题过程。
实际测试发现:当前页为1,每页显示2条,查询的点赞列表信息分页正常,查询出资讯列表信息为6条(总条数);当前页为2,每页显示2条,查询的点赞列表信息分页正常,查询出资讯列表信息也是6条(总条数);分页失效!!
PageInterceptor
,每一个涉及数据库查询的mapper方法都会被该拦截器进行拦截,里面执行的主要逻辑是查询总数处理和执行分页处理。
PageInterceptor.java
中拦截处理逻辑:
@Override
public Object intercept(Invocation invocation) throws Throwable {
try {
// 省略部分代码
//调用方法判断是否需要进行分页,如果不需要,直接返回结果,最终是判断是否存在ThreadLocal,继续往下看
if (!dialect.skip(ms, parameter, rowBounds)) {
// 省略部分代码
//判断是否需要进行 count 查询,就是决定控制台是否显示select count(0)
if (dialect.beforeCount(ms, parameter, rowBounds)) {
// 处理总条数的逻辑省略
}
//返回 true 时继续分页查询,false 时直接返回
if (!dialect.afterCount(count, parameter, rowBounds)) {
//当查询总数为 0 时,直接返回空的结果
return dialect.afterPage(new ArrayList(), parameter, rowBounds);
}
}
//判断是否需要进行分页查询
if (dialect.beforePage(ms, parameter, rowBounds)) {
//生成分页的缓存 key
CacheKey pageKey = cacheKey;
//处理参数对象
parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
//调用方言获取分页 sql
String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
// 组装BoundSql,实际就是在业务代码后面拼接limit ?,?
BoundSql pageBoundSql = new BoundSql(configuration, pageSql, boundSql.getParameterMappings(), parameter);
//执行分页查询
resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
} else {
//不执行分页的情况下,也不执行内存分页
resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
}
} else {
//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
return dialect.afterPage(resultList, parameter, rowBounds);
} finally {
// 最终执行的是清空ThreadLocal操作,LOCAL_PAGE.remove()
dialect.afterAll();
}
}
一般在业务代码中都会写PageHelper.startPage(currentPage,pageSize)
,它的主要作用是设置ThreadLocal
信息,其底层执行源码如下(setLocalPagef方法
):
public abstract class PageMethod {
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
protected static void setLocalPage(Page page) {
LOCAL_PAGE.set(page);
}
public static <T> Page<T> getLocalPage() {
return LOCAL_PAGE.get();
}
public static void clearPage() {
LOCAL_PAGE.remove();
}
// 省略部分代码
}
PageInterceptor.java中判断是否支持分页主要是根据能否存在ThreadLocal
,如果没有则不进行分页操作。上文已经说过,每个mapper查询都会经过拦截器处理,拦截器处理的最后一步是dialect.afterAll()
,最终执行的是LOCAL_PAGE.remove()
,即移除本地变量。这也就是为什么案例中执行第一个mapper查询会按照指定页数和每页显示条数查询出对应分页数据(因为存在ThreadLocal
),而第二个mapper查询的是所有(因为第一个mapper查询完成之后会将ThreadLocal
进行清除)。清楚原因之后如何处理就简单了。如果同一个方法中多个mapper都需要支持分页操作,那都保证每个mapper前面都进行ThreadLocal
初始化赋值操作。修改后代码如下:
TestPageHelper.java
:
@RestController
@RequestMapping("/testPageHelper")
public class TestPageHelper {
@Autowired
private PageHelperServiceImpl pageHelperService;
@GetMapping
public ResultVo<PageInfo<News>> findNews(Integer currentPage,Integer pageSize){
List<News> newsList = pageHelperService.findNews(currentPage,pageSize);
PageInfo<News> newsPageInfo = new PageInfo<News>(newsList);
newsPageInfo.setList(newsList);
return ResultVoUtil.success(newsPageInfo);
}
}
PageHelperServiceImpl.java
:
@Service
public class PageHelperServiceImpl {
@Autowired
private NewsMapper newsMapper;
// 查询资讯信息
public List<News> findNews(Integer currentPage,Integer pageSize){
PageHelper.startPage(currentPage,pageSize);
// 查询点赞信息
List<FindLike> like = newsMapper.findLike();
PageHelper.startPage(currentPage,pageSize);
// 查询资讯列表
List<News> news = newsMapper.findNews();
return news;
}
}
总结:对指定mapper查询支持分页,前面一定要有PageHelper.startPage(currentPage,pageSize)
,不能有其他mapper查询,否则会失效!
欢迎评论区点赞留言,相互交流,共同进步!