PageHelper
如何使用?
1:添加依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.5</version>
</dependency>
版本根据需要自行选择
https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter
第一步写一个没有分页的接口。
@GetMapping("noPage")
public java.util.List<Doc> list() {
return mapper.selectList(null);
}
这个我相信都可以写的出来。
第二步使用PageInfo包裹的接口
@GetMapping("pageInfo")
public PageInfo<Doc> pageInfo(Integer pageNum, Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
return new PageInfo<>(mapper.selectList(null));
}
返回值省略list
{
"total": 20,
"list": [
],
"pageNum": 2,
"pageSize": 3,
"size": 3,
"startRow": 4,
"endRow": 6,
"pages": 7,
"prePage": 1,
"nextPage": 3,
"isFirstPage": false,
"isLastPage": false,
"hasPreviousPage": true,
"hasNextPage": true,
"navigatePages": 8,
"navigatepageNums": [
1,
2,
3,
4,
5,
6,
7
],
"navigateFirstPage": 1,
"navigateLastPage": 7
}
第三步使用Page包裹返回接口
@GetMapping("page")
public Page<Doc> page(Integer pageNum, Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
Page<Doc> page = (Page<Doc>) mapper.selectList(null);
logger.info(page.toString());
return page;
}
测试发现page就是一个分页后的list,不带其他参数。
Page{count=true, pageNum=3, pageSize=4, startRow=8, endRow=12, total=20, pages=5, reasonable=false,
pageSizeZero=false}
第四步将第三步的page对象放入构造函数。
@GetMapping("pageToPageInfo")
public PageInfo<Doc> pageToPageInfo(Integer pageNum, Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
Page<Doc> page = (Page<Doc>) mapper.selectList(null);
return new PageInfo<>(page);
}
第五步自定义PageResult作为返回值
@Data
public class PageResult<T> {
private static final Logger logger = LoggerFactory.getLogger(PageResult.class);
/**
* 第几页
*/
private int pageNum;
/**
* 页面大小
*/
private int pageSize;
/**
* 总页数
*/
private int pages;
/**
* 总数
*/
private long total;
/**
* 数据
*/
private List<T> list;
/**
* 构造函数
*
* @param list
*/
public PageResult(List<T> list) {
if (list instanceof Page) {
Page<T> page = (Page<T>) list;
this.pageNum = page.getPageNum();
this.pageSize = page.getPageSize();
this.pages = page.getPages();
this.total = page.getTotal();
}
this.list = list;
}
}
@GetMapping("pageToPageResult")
public PageResult<Doc> pageToPageResult(Integer pageNum, Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
return new PageResult<>(mapper.selectList(null));
}
{
"pageNum": 2,
"pageSize": 3,
"pages": 7,
"total": 20,
"list": [
]
}
借鉴了这段实现逻辑直接从Page中拿到所需参数。
之所以我们自定义的PageResult可以成功是因为 list instanceof Page 这一行代码,mapper.selectList 经过处理后返回的不是普通的List而是Page对象。
复杂分页
对象转换
一般来说我们拿到的DO和返回前端的VO不是同一个对象会进行相关处理,比如增删字段,这个时候如何正确分页呢?
先演示一个错误案例
@GetMapping("changeV1")
public PageInfo<DocQueryRespVO> changeV1(Integer pageNum, Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<Doc> sourceList = mapper.selectList(null);
List<DocQueryRespVO> targetList = sourceList.stream().map((source) -> {
DocQueryRespVO target = new DocQueryRespVO();
BeanUtils.copyProperties(source, target);
Integer docType = target.getDocType();
String msg = DocTypeEnum.getMsgByCode(docType);
target.setDocTypeName(msg);
return target;
}).collect(Collectors.toList());
return new PageInfo<>(targetList);
}
对原list每个对象修改字段值
{
"total": 5,
"list": [
],
"pageNum": 1,
"pageSize": 5,
"size": 5,
"startRow": 0,
"endRow": 4,
"pages": 1,
"prePage": 0,
"nextPage": 0,
"isFirstPage": true,
"isLastPage": true,
"hasPreviousPage": false,
"hasNextPage": false,
"navigatePages": 8,
"navigatepageNums": [
1
],
"navigateFirstPage": 1,
"navigateLastPage": 1
}
可以看到这个total是错误的,应该是二十才对。分析一下为什么返回5。
public PageInfo(List<? extends T> list) {
this(list, DEFAULT_NAVIGATE_PAGES);
}
public PageInfo(List<? extends T> list, int navigatePages) {
super(list);
}
public PageSerializable(List<? extends T> list) {
this.list = (List<T>) list;
if(list instanceof Page){
this.total = ((Page<?>)list).getTotal();
} else {
this.total = list.size();
}
}
依次点进去发现,由于instanceof 失败所以取了list的total导致失效。可以加入代码验证 instanceof 输出。
System.out.println(targetList instanceof Page);
方法一
@GetMapping("changeV2")
public PageInfo changeV2(Integer pageNum, Integer pageSize) {
//省略部分代码
PageInfo pageInfo = new PageInfo(sourceList);
pageInfo.setList(targetList);
return pageInfo;
}
核心思想,利用泛型直接替换list保留原list中的分页信息。
方法二
@GetMapping("changeV3")
public PageInfo<DocQueryRespVO> changeV3(Integer pageNum, Integer pageSize) {
//省略部分代码
return PageHelperUtil.pageInfoCopy(sourceList, targetList);
}
封装一个工具类
public static <source, target> PageInfo<target> pageInfoCopy(List<source> sList, List<target> tList) {
if (org.springframework.util.CollectionUtils.isEmpty(sList) ||
org.springframework.util.CollectionUtils.isEmpty(tList)) {
return null;
}
PageInfo<source> sourcePage = new PageInfo<>(sList);
PageInfo<target> targetPage = new PageInfo<>(tList);
org.springframework.beans.BeanUtils.copyProperties(sourcePage, targetPage);
targetPage.setList(tList);
return targetPage;
}
核心思想利用BeanUtils.copyProperties将分页信息拷贝过来,仅仅替换list数据。
手动分页
public static <T> PageInfo<T> pageManual(List<T> list, int pageNum, int pageSize) {
PageInfo<T> pageInfo = new PageInfo<>();
//计算总页数
int total = list.size();
//如果总数为0直接返回
if (total == 0) {
return pageInfo;
}
// 计算最大页数
int pageNumTotal;
if (total <= pageSize) {
pageNumTotal = 1;
} else {
pageNumTotal = total % pageSize == 0 ? (total / pageSize) : (total / pageSize) + 1;
}
//超出最大页码返回最后一页
if (pageNum > pageNumTotal) {
pageNum = pageNumTotal;
}
//如果页码 <= 0 返回第一页
if (pageNum <= 0) {
pageNum = 1;
}
//开始分页
int st;
int et;
// 1 <= pageNum <= pageNumTotal
if (pageNum < pageNumTotal) {
st = (pageNum - 1) * pageSize;
et = st + pageSize;
} else {
st = (pageNum - 1) * pageSize;
et = total;
}
//subList包含st-et-1
List<T> result = list.subList(st, et);
pageInfo.setPageNum(pageNum);
pageInfo.setPageSize(pageSize);
pageInfo.setList(result);
pageInfo.setTotal(total);
pageInfo.setPages(pageNumTotal);
pageInfo.setIsLastPage(pageNumTotal == pageSize);
return pageInfo;
}
mybatisplus
BaseMapper
主要分页方法
<P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
<P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
如何使用
第一步注册插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
第二步写一个简单的接口
@GetMapping("selectPage")
public Page<Doc> selectPage(Integer pageNum, Integer pageSize) {
return mapper.selectPage(new Page<>(pageNum, pageSize), null);
}
分页结果
{
"records": [
{
},
{
},
{
},
{
},
{
}
],
"total": 35,
"size": 5,
"current": 1,
"orders": [],
"optimizeCountSql": true,
"searchCount": true,
"countId": null,
"maxLimit": null,
"pages": 7
}
原理
MybatisAutoConfiguration 中关键代码如下
public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,
ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider,
ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,
ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider,
ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizers) {
this.interceptors = interceptorsProvider.getIfAvailable();
}
当我们注册 MybatisPlusInterceptor 这个 bean后,interceptorsProvider.getIfAvailable()可以拿到Interceptor。
复杂分页
对象转换
Page page = mapper.selectPage(new Page<>(pageNum, pageSize), null);
List<Doc> records = page.getRecords();
return page.setRecords(targetList);
和上面的差不多,都是取消泛型的编译检查来完成分页。