深入浅出mybatis分页

MyBatis 分页插件 PageHelper

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);

和上面的差不多,都是取消泛型的编译检查来完成分页。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值