PageHelper 实现分页查询以及其实现原理

介绍

常规分页,我们可以在sql语句中,添加limit等,来实现分页查询,但是比较繁琐
下面演示使用PageHelper来实现分页查询,使用时代码量极少
PageHelper是Mybatis提供的分页插件,目前支持Oracle,Mysql,MariaDB,SQLite等数据库。

原理

Pagehelper内部实现了一个PageInterceptor拦截器
Mybatis会加载这个拦截器到拦截器链中
在我们使用过程中先使用PageHelper.startPage这样的语句
会在当前线程上下文中设置一个ThreadLocal变量
再利用PageInterceptor这个分页拦截器拦截
当查询执行时,会从ThreadLocal中拿到分页的信息,如果有分页信息,则会拼装分页SQL(limit语句等)进行分页查询
最后再把ThreadLocal中的东西清除掉
在mapper声明时,直接使用List类型即可
但实际最终返回的数据类型为com.github.pagehelper.Page,此类继承自ArrayList
所以在执行 PageInfo pageInfo = new PageInfo<>(list) 的时候,pageInfo变量才会自动填充跟分页相关的数据
new PageInfo<>(list)最终调用的构造函数代码

public PageInfo(List<T> list, int navigatePages) {
    super(list);
    this.isFirstPage = false;
    this.isLastPage = false;
    this.hasPreviousPage = false;
    this.hasNextPage = false;
    if (list instanceof Page) {
        Page page = (Page)list;
        this.pageNum = page.getPageNum();
        this.pageSize = page.getPageSize();
        this.pages = page.getPages();
        this.size = page.size();
        if (this.size == 0) {
            this.startRow = 0L;
            this.endRow = 0L;
        } else {
            this.startRow = page.getStartRow() + 1L;
            this.endRow = this.startRow - 1L + (long)this.size;
        }
    } else if (list instanceof Collection) {
        this.pageNum = 1;
        this.pageSize = list.size();
        this.pages = this.pageSize > 0 ? 1 : 0;
        this.size = list.size();
        this.startRow = 0L;
        this.endRow = list.size() > 0 ? (long)(list.size() - 1) : 0L;
    }
    
    if (list instanceof Collection) {
        this.calcByNavigatePages(navigatePages);
    }
    
}
  

依赖

<!-- MyBatis Spring Boot:数据访问层MyBatis编程 -->
<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>2.2.0</version>
</dependency>
<!-- PageHelper Spring Boot:MyBatis分页 -->
<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper-spring-boot-starter</artifactId>
  <version>1.4.0</version>
</dependency>

如不需要连接数据库,只是单纯的声明或调用PageHelper内的信息(一般在微服务中会用到),则添加

<!-- PageHelper Spring Boot:MyBatis分页 -->
<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper</artifactId>
  <version>5.3.0</version>
</dependency>

基本使用

准备数据库数据

CREATE TABLE `order_tbl`  (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '订单id',
  `user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户id',
  `commodity_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品编码,也可以是商品id',
  `count` int NULL DEFAULT 0 COMMENT '购买这个商品的数量',
  `money` int NULL DEFAULT 0 COMMENT '订单金额',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 27 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

插入测试数据

insert  into `order_tbl`(`id`,`user_id`,`commodity_code`,`count`,`money`) values
(1,'UU100','PU201',10,200),
(2,'UU100','PU201',10,200),
(3,'UU100','PU201',10,200),
(4,'UU100','PU201',10,200);

application配置文件

因为在表字段中存在下划线(_),所以使用此配置开启Mybatis自动转驼峰命名

mybatis:
  configuration:
    map-underscore-to-camel-case: true

1.创建实体类

package cn.tedu.csmall.commons.pojo.order.model;

import lombok.Data;
import java.io.Serializable;

@Data
public class Order implements Serializable {

    private Integer id;
    private String userId;
    private String commodityCode;
    private Integer count;
    private Integer money;

}

2.编写持久层

在持久层编写全查相关mapper,此处直接使用注解,未使用xml去编写,真实业务环境中推荐使用xml去编写

package cn.tedu.csmall.order.webapi.mapper;

import cn.tedu.csmall.commons.pojo.order.model.Order;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

import java.util.List;


@Repository
public interface OrderMapper {
    
    @Select("select id, user_id, commodity_code, count, money from order_tbl")
    List<Order> listOrder();
}

3.编写业务逻辑层

3.1编写业务接口

package cn.tedu.csmall.order.service;

import cn.tedu.csmall.commons.pojo.order.model.Order;
import com.github.pagehelper.PageInfo;

import java.util.List;


public interface IOrderService {
    PageInfo<Order> listOrder(Integer pageNum, Integer pageSize);
}

3.2编写业务实现类

package cn.tedu.csmall.order.webapi.service.impl;

import cn.tedu.csmall.commons.pojo.order.model.Order;
import cn.tedu.csmall.order.service.IOrderService;
import cn.tedu.csmall.order.webapi.mapper.OrderMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;


@Service
@Slf4j
public class OrderServiceImpl implements IOrderService {
    //装配持久层操作对象
    @Autowired
    private OrderMapper orderMapper;


    @Override
    public PageInfo<Order> listOrder(Integer pageNum, Integer pageSize) {
        //设置分页要求,pageNum为页码从1开始,pageSize为单页尺寸
        PageHelper.startPage(pageNum, pageSize);
        //设置完分页信息后,会自动在下一次sql查询中,添加分页信息
        //原sql语句:
        //select id, user_id, commodity_code, count, money from order_tbl
        //处理后为:
        //select id, user_id, commodity_code, count, money from order_tbl limit (pageNum-1)*pageSize, pageSize
        //此时查询的结果,实际上是一个Page对象(继承了ArrayList),可以使用List去声明,在此对象中,保存了分页相关的其他信息
        List<Order> list = orderMapper.listOrder();
        //所以在此处使用new PageInfo<>(list)时,pageInfo变量才会自动填充跟分页相关的数据
        PageInfo<Order> pageInfo = new PageInfo<>(list);
        return pageInfo;
    }
}

3.3编写控制器

package cn.tedu.csmall.order.webapi.controller;

import cn.tedu.csmall.commons.pojo.order.model.Order;
import cn.tedu.csmall.order.service.IOrderService;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/base/order")
public class OrderController {
    @Autowired
    private IOrderService orderService;

    //实际业务中通常是使用统一响应的,此处仅为测试,就不再进行包装
    @PostMapping("/get")
    public PageInfo<Order> listOrder(Integer pageNum, Integer pageSize) {
        PageInfo<Order> pageInfo = orderService.listOrder(pageNum, pageSize);
        return pageInfo;
    }


}

测试

Postman测试结果

在这里插入图片描述

响应内容

{
  "total": 4,
  "list": [
    {
      "id": 1,
      "userId": "UU100",
      "commodityCode": "PU201",
      "count": 10,
      "money": 200
    },
    {
      "id": 2,
      "userId": "UU100",
      "commodityCode": "PU201",
      "count": 10,
      "money": 200
    },
    {
      "id": 3,
      "userId": "UU100",
      "commodityCode": "PU201",
      "count": 10,
      "money": 200
    }
  ],
  "pageNum": 1,
  "pageSize": 3,
  "size": 3,
  "startRow": 1,
  "endRow": 3,
  "pages": 2,
  "prePage": 0,
  "nextPage": 2,
  "isFirstPage": true,
  "isLastPage": false,
  "hasPreviousPage": false,
  "hasNextPage": true,
  "navigatePages": 8,
  "navigatepageNums": [
    1,
    2
  ],
  "navigateFirstPage": 1,
  "navigateLastPage": 2
}

运行过程

调用2次接口,pageSize统一使用3,pageNum分别使用1跟2

查询第1页数据

2022-08-29 16:23:11.882 DEBUG 35624 --- [io-20002-exec-1] c.t.c.o.w.m.OrderMapper.listOrder_COUNT  : ==>  Preparing: SELECT count(0) FROM order_tbl
2022-08-29 16:23:11.899 DEBUG 35624 --- [io-20002-exec-1] c.t.c.o.w.m.OrderMapper.listOrder_COUNT  : ==> Parameters: 
2022-08-29 16:23:11.916 DEBUG 35624 --- [io-20002-exec-1] c.t.c.o.w.m.OrderMapper.listOrder_COUNT  : <==      Total: 1
2022-08-29 16:23:11.919 DEBUG 35624 --- [io-20002-exec-1] c.t.c.o.w.mapper.OrderMapper.listOrder   : ==>  Preparing: select id, user_id, commodity_code, count, money from order_tbl LIMIT ?
2022-08-29 16:23:11.920 DEBUG 35624 --- [io-20002-exec-1] c.t.c.o.w.mapper.OrderMapper.listOrder   : ==> Parameters: 3(Integer)
2022-08-29 16:23:11.921 DEBUG 35624 --- [io-20002-exec-1] c.t.c.o.w.mapper.OrderMapper.listOrder   : <==      Total: 3

SELECT count(0) FROM order_tbl
select id, user_id, commodity_code, count, money from order_tbl LIMIT 3

查询第二页数据

2022-08-29 16:25:16.157 DEBUG 35624 --- [io-20002-exec-3] c.t.c.o.w.m.OrderMapper.listOrder_COUNT  : ==>  Preparing: SELECT count(0) FROM order_tbl
2022-08-29 16:25:16.158 DEBUG 35624 --- [io-20002-exec-3] c.t.c.o.w.m.OrderMapper.listOrder_COUNT  : ==> Parameters: 
2022-08-29 16:25:16.158 DEBUG 35624 --- [io-20002-exec-3] c.t.c.o.w.m.OrderMapper.listOrder_COUNT  : <==      Total: 1
2022-08-29 16:25:16.159 DEBUG 35624 --- [io-20002-exec-3] c.t.c.o.w.mapper.OrderMapper.listOrder   : ==>  Preparing: select id, user_id, commodity_code, count, money from order_tbl LIMIT ?, ?
2022-08-29 16:25:16.159 DEBUG 35624 --- [io-20002-exec-3] c.t.c.o.w.mapper.OrderMapper.listOrder   : ==> Parameters: 3(Long), 3(Integer)
2022-08-29 16:25:16.160 DEBUG 35624 --- [io-20002-exec-3] c.t.c.o.w.mapper.OrderMapper.listOrder   : <==      Total: 1
SELECT count(0) FROM order_tbl
select id, user_id, commodity_code, count, money from order_tbl LIMIT 3,3

通过观察日志,可以得知运行的过程
查询分为2次,第一次查询总数量,第二次查询当前页面数据

优化

由于PageInfo返回信息较为复杂,数据过多,实际使用中,经常会自己包装响应类,来简化信息,一般只响应当前页码,总页数,总数量,以及数据
参考:

package cn.tedu.mall.common.restful;

import com.github.pagehelper.PageInfo;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
 * 分页数据封装
 */
@Data
public class JsonPage<T> implements Serializable {
    private Integer pageIndex;
    private Integer pageSize;
    private Integer totalPage;
    private Long total;
    private List<T> list;

    public static <T> JsonPage<T> restPage(PageInfo<T> pageInfo) {
        JsonPage<T> result = new JsonPage<>();
        result.setTotalPage(pageInfo.getPages());
        result.setPageIndex(pageInfo.getPageNum());
        result.setPageSize(pageInfo.getPageSize());
        result.setTotal(pageInfo.getTotal());
        result.setList(pageInfo.getList());
        return result;
    }

}

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值