使用 PageHelper 前先学会手写一个分页查询吧

🔊 本文收纳于 ⭐ CS-Wiki(Gitee 推荐项目), 欢迎 star ~ 😊


前言

当今框架层出不穷,程序员几乎不得不以年为单位疯狂更新技术栈,但万变不离其宗,了解这些框架的底层原理,才能够更好的掌握框架,而不是被不断迭代的框架所支配

分页是日常开发中很常见的需求,本文带大家基于 MyBatis 和 Spring Boot 一步一步写一个分页查询,了解分页查询的基本逻辑。当然,大家熟悉之后,日常开发中还是推荐利用插件/框架来提高编码效率(比如非常优秀的 MyBatis 分页插件 PageHelper)。

1. 从分页效果入手讲解该如何做

我们要实现的分页效果如下:

分页栏显示当前页以及当前页的前三页和后三页。

分页栏中的页码我们会用一个 ArrayList 存储起来,表示在当前页能够跳转哪些页码。比如当前页是 5 的时候,ArrayList 中就会存 [2,3,4,5,6,7,8], 当前页是 3 的时候,ArrayList 中就会存 [1,2,3,4,5,6]

💦 分页栏中的第一页按钮 << 和最后一页按钮 >> 的基本显示逻辑如下:

  • 当页码列表 ArrayList 中不包含第一页的页码(比如 1)的时候,就显示第一页按钮,否则不显示
  • 当页码列表 ArrayList 中不包含最后一页的页码(比如 8) 的时候,就显示最后一页按钮,否则不显示

💦 分页栏中的上一页按钮 <,下一页按钮 > 的基本显示逻辑如下:

  • 当前页如果不是第一页,就显示上一页按钮,否则不显示
  • 当前页如果不是最后一页,就显示下一页按钮,否则不显示

至于后端如何获取到当前页码,很简单,我们在请求的 url 后面加上表示当前页码的 pageNumber 字段,利用 Spring Boot 的注解 @RequestParam("pageNumber") 就可以获取当前页码了

2. 用于分页的 SQL 语法

分页的关键就是这个 SQL 语句:

select * from tableName limit offset size

这条语句的含义是:从 tableName 表中的第 offset 条记录开始(注意 offset 从零开始),查询出 size 个记录。

举个例子,category 表中数据如下:

运行下边这条语句:查询从第 2 条记录(即 id = 3)开始的连续 5 条记录

select * from category limit 2, 5;

3. 承载分页信息的实体类

分页信息实体类包含如下数据:

  • 是否显示上一页按钮 showPrevious
  • 是否显示下一页按钮 showNext
  • 是否显示跳转到第一页按钮 showFirstPage
  • 是否显示跳转到最后一页按钮 showEndPage
  • 当前页码 pageNumber
  • 当前页可跳转的页码列表 pageNumbers
  • 总页码数 totalPage
  • 当前页要显示的具体数据
/**
 * 分页所需要的信息
 */
public class PaginationDTO {
    private boolean showPrevious; // 是否显示上一页按钮
    private boolean showNext; // 是否显示下一页按钮
    private boolean showFirstPage; // 是否显示跳转到第一页按钮
    private boolean showEndPage; // 是否显示跳转到最后一页按钮

    private Integer pageNumber; // 当前页码
    private List<Integer> pageNumbers = new ArrayList<>(); // 当前页可跳转的页码列表
    private Integer totalPage; // 总页码数
    
    private List<Question> questionList; // 当前页要显示的具体数据
    
    // Getter and Setter

4. 根据当前页码设置分页所需要的信息

这段代码做的事情就是我们在第 1 节分析的,决定上一页、下一页、第一页和最后一页按钮是否显示。

显然,这段代码应该放在承载分页信息的实体类 PaginationDTO 中:

/**
 * 分页所需要的信息
 */
@Data
public class PaginationDTO {

    ......

    /**
     * 根据当前页码设置相关分页信息
     * @param totalPage 页码总数
     * @param pageNumber 当前页码
     */
    public void setPagination(Integer totalPage, Integer pageNumber) {
        this.totalPage = totalPage;
        this.pageNumber = pageNumber;

        // 页码列表的显示:显示当前页和当前页的前三页和后三页,可为空
        pageNumbers.add(pageNumber);
        for (int i = 1; i <= 3; i++) {
            if (pageNumber - i > 0) {
                pageNumbers.add(0, pageNumber - i);
            }

            if (pageNumber + i <= totalPage) {
                pageNumbers.add(pageNumber + i);
            }
        }

        // 是否展示上一页按钮
        if (pageNumber == 1) {
            showPrevious = false;
        }
        else {
            showPrevious = true;
        }
        // 是否展示下一页按钮
        if (pageNumber.equals(totalPage)) {
            showNext = false;
        }
        else {
            showNext = true;
        }

        // 是否展示跳转到第一页按钮
        if (pageNumbers.contains(1)) {
            // 如果当前页可跳转的页码列表包含第一页,则不展示跳转到第一页按钮
            showFirstPage = false;
        }
        else {
            showFirstPage = true;
        }
        // 是否展示跳转到最后一页按钮
        if (pageNumbers.contains(totalPage)) {
            // 如果当前页可跳转的页码列表包含最后一页,则不展示跳转到最后一页按钮
            showEndPage = false;
        }
        else {
            showEndPage = true;
        }
    }
}

5. 分页查询请求入口

Controller 层:

@Controller
public class IndexController {

    @Autowired
    private QuestionService questionService;

    /**
     * @param request
     * @param model
     * @param pageNumber 第多少页,默认第 1 页
     * @param pageSize 每页显示的问题数量, 默认为 10
     * @return
     */
    @GetMapping("/")
    public String index(HttpServletRequest request, Model model,
                        @RequestParam(name = "pageNumber", defaultValue = "1") Integer pageNumber,
                        @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
        
        .........

        // 获取问题列表需要的信息
        PaginationDTO pagination = questionService.list(pageNumber, pageSize);
        model.addAttribute("pagination", pagination);

        ......
    }
}

对应的前端:(Thymeleaf 模板引擎),利用 th:each 标签循环页码列表,然后在 url 后面加上 pageNumber,使得后端能够通过 RequestParam 获取到当前页码

<li th:each="pageNumber : ${pagination.pageNumbers}" th:class="${pagination.pageNumber == pageNumber}? 'active' : ''">
    <a th:href="@{/(pageNumber = ${pageNumber})}" th:text="${pageNumber}"></a>
</li>

上一页、下一页、第一页和最后一页按钮的前端代码就不贴了,大同小异。

QuestionService 如下 👇

6. 分页查询

前面说过,页面承载的信息是 Question,我们建立一个 QuestionService.list 方法 用于分页查询,具体包含:

  1. 页码总数 totalPage 的计算

  2. 页码列表的容错处理。比如总页码只有 8 页,但是我们在地址栏手动修改为 12 页,我们需要它在分页栏高亮最后一页并显示最后一页数据,而不是第 12 页,第 12 页是不存在的。

  1. 分页查询数据。涉及每页起始索引的计算
  • 第 1 页:起始索引 0,limit 0, 10
  • 第 2 页:起始索引 10,limit 10, 20
  • 第 3 页:起始索引 30,limit 20, 30

推出 => 若当前页码为 i,每页显示 10 条数据,则每页的起始索引 offset = 10 * (i - 1)

@Service
public class QuestionService {

    @Autowired
    private QuestionMapper questionMapper;

    /**
     * 分页查询
     * @param pageNumber 第多少页(当前页码)
     * @param pageSize 每页显示的问题数量
     * @return
     */
    public PaginationDTO list(Integer pageNumber, Integer pageSize) {

        PaginationDTO paginationDTO = new PaginationDTO();
        Integer totalCount = questionMapper.count(); // 问题总数
        Integer totalPage; // 页码总数
		
        // 计算页码总数
        if (totalCount % pageSize == 0) {
            totalPage = totalCount / pageSize;
        }
        else {
            totalPage = totalCount / pageSize + 1;
        }

        // 页码列表的容错处理
        if (pageNumber < 1) {
            pageNumber = 1;
        }
        if(pageNumber > totalPage) {
            pageNumber = totalPage;
        }

        paginationDTO.setPagination(totalPage, pageNumber);

        Integer offset = pageSize * (pageNumber - 1); // 每页的起始索引
        List<Question> questions = questionMapper.list(offset, pageSize); // 分页查询当前页的具体数据
        paginationDTO.setQuestionList(questions); // 存储当前页的具体数据
        return paginationDTO;
    }
}

对应的 MyBatis 的 mapper 文件 QuestionMapper 如下:

@Select("select * from question limit #{offset}, #{pageSize}")
List<Question> list(@Param("offset") Integer offset, @Param("pageSize") Integer pageSize);

🚨 MyBatis 约定当我们传入的参数不是实体类对象的时候,需要利用 @Param 自己完成映射

总结

以上。全文应该没啥槽点,分页查询的逻辑并不难,不过自己上手做的话需要兼顾前后端可能还是会出现一些问题,大家最好还是动手做一做。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
MyBatis PageHelper一个 MyBatis 分页插件,能够快速、便捷的进行分页查询,支持多种数据库。使用 PageHelper 可以避免手写 SQL 语句进行分页操作,同时 PageHelper 支持物理分页和逻辑分页两种方式。 下面是使用 PageHelper 进行分页查询的步骤: 1. 导入 PageHelper 依赖 Maven 项目在 pom.xml 文件中添加以下依赖: ``` <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.11</version> </dependency> ``` 2. 配置 PageHelperMyBatis 的配置文件中添加以下配置: ``` <plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <property name="dialect" value="mysql"/> </plugin> </plugins> ``` 其中 dialect 属性指定了数据库类型,PageHelper 支持的数据库类型包括:oracle、mysql、mariadb、sqlite、hsqldb、postgresql、db2、sqlserver、informix、达梦、人大金仓、南大通用、神通、PostgreSQL9.3-9.5。 3. 使用 PageHelper 进行分页查询 在需要进行分页查询的方法中使用 PageHelper.startPage 方法进行分页设置,然后调用查询方法获取查询结果。例如: ``` PageHelper.startPage(1, 10); // 第一页,每页显示 10 条记录 List<User> userList = userDao.selectUserList(); // 查询用户列表 PageInfo<User> pageInfo = new PageInfo<>(userList); // 封装分页结果 ``` 其中 PageHelper.startPage 方法接收两个参数,第一个参数为当前页码,第二个参数为每页显示的记录数。 最后使用 PageInfo 类对查询结果进行封装,得到分页结果。PageInfo 类中包含了分页信息和查询结果。 以上就是使用 MyBatis PageHelper 进行分页查询的基本步骤。需要注意的是,在使用 PageHelper 进行分页查询时,需要确保查询语句中不要使用 limit 关键字。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞天小牛肉

您的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值