Spring Page分页查询实现


一、分页查询

1. 实现机制

假分页即全量从数据库读取,再利用前端组件进行分页展示。这么做虽然最简单粗暴,但面对大数据量不仅对数据库压力大,而且前端一次性将大量数据载入内存中会大大降低页面响应速度。

Antd 中的 a-table 默认就是假分页,后端数据一次性加载到前端页面再由前端实现渲染,适合数据量不大的常见,这里就不具体举例了。

相应对假分页即真分页,故顾名思义即通过如 MySQLlimit 关键字降低数据库压力,在大数据量时能够有更高的效率但是实现相对更为复杂。

在数据库查询时通过 limit 关键字实现分页查询,不同数据库的关键字不同这里以 MySQL 举例,如下将查询的数据区间为 [2, 5],即第 2 条数据至第 5 条。

select
    id, name, gender, create_time
from sys_user
limit 2, 5;

对于前端表格分页通常存在两个重要参数:PageIndexPageLimit,分别代表当前页数与每页展示的数量。如 PageIndex = 2, PageLimit = 10 的情况下,查询的数据条数应该为第 11 ~ 20 条数据,即上述 SQLlimit 的两个参数计算公式如下:

  • 参数一:(PageIndex - 1) * PageLimit + 1
  • 参数二:PageLimit

2. 依赖引入

为了实现数据分页功能在工程中引入下述依赖:

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-commons</artifactId>
</dependency>

3. 数据封装

(1) Service

Spring Boot 中自带了 Page 对象提供了数据分页服务,实现分页数据的对象封装。

注意一点的是我们无需手动根据之前提到的计算公式换算 offsetlimit 的值,框架已经替我们进行处理。同时在 PageRequestoffset 即上述提到 PageIndex 其对应的初始下标为 0,即 offset = 0, limit = 10 查询的数据条数应该为第 1 ~ 10 条数据。

@Service("sysUserService")
public class SysUserServiceImpl implements SysUserService {
    @Override
    public Page<SysUser> queryByPage(SysUser sysUser, int offset, int limit) {
        // 构造分页条件
        PageRequest request = PageRequest.of(offset, limit);
        // 分页查询数据
        long total = this.sysUserDao.count(sysUser);
        List<SysUser> data = sysUserDao.queryAllByLimit(sysUser, request)
        // 构造分页对象
        Page<SysUser> pageInfo = new PageImpl<>(data, request, total);
        return pageInfo;
    }
}

@Mapper
public interface SysUserDao {
    List<SysUser> queryAllByLimit(SysUser sysUser, @Param("pageable") Pageable pageable);
}
(2) SQL

SQL 命令中通过 limit 关键字实现分页查询。

<select id="queryAllByLimit" resultMap="SysUserMap">
    select *
    from sys_user
    <where>
        <if test="sysUser.id != null">
            and id = #{id}
        </if>
        <if test="sysUser.username != null and sysUser.username != ''">
            and username = #{username}
        </if>
        <if test="sysUser.password != null and sysUser.password != ''">
            and password = #{password}
        </if>
    </where>
    limit #{pageable.offset}, #{pageable.pageSize}
</select>

4. 前端设计

这里以 Antd 表格为例,在标签内添加 pagination 属性。

<a-table
    :columns="columns"
    :data-source="data"
    :pagination="pagination"
    :bordered="false"
/>

pagination 具体定义参数如下,在 onShowSizeChange()onChange() 事件中调用之前定义的后端分页查询接口即可。

其中 updatePage() 方法即根据前端变更的页数调用后端中定义的分页查询接口。

data() {
    return {
        data: [],
        tableData: [],
        pageRequest: {
            offset: 1,
            limit: 5
        },
        pagination: {
            total: 0, // 总数,必须先有
            defaultCurrent: 1, // 默认当前页数
            defaultPageSize: 5, // 默认当前页显示数据的大小
            showSizeChanger: true,
            showQuickJumper: true,
            pageSizeOptions: ["5", "10", "15", "20"],
            showTotal: (total) => `共 ${total} 条`, // 显示总数
            // 改变每页数量触发
            onShowSizeChange: (current, pageSize) => {
                this.pagination.defaultCurrent = 1
                this.pagination.defaultPageSize = pageSize
                this.updatePage(current, pageSize)
            },
            // 点击页数触发
            onChange: (current, size) => {
                this.pagination.defaultCurrent = current
                this.pagination.defaultPageSize = size
                this.updatePage(current, size)
            }
        }
    }
}

二、分页插件

1. 插件介绍

除了 Spring 提供的分页功能外,Pagehelper 同样也是一样使用广泛的分页插件。

同样需要在工程中 pom 文件引入其对应的依赖信息。

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>2.1.0</version>
</dependency>

2. 插件配置

除了引入对应的 maven 依赖外还需要再工程中 application.yml 文件中配置下述属性:

pagehelper:
  helperDialect: mysql
  reasonable: true
  supportMethodsArguments: true
  params: count=countSql

3. 使用实例

PageHelper 使用十分简单,只需通过 PageHelper.startPage() 设置分页参数即可,这里的 list() 查询语句中则无需再手动拼接 limit 关键字。

PageHelper 实现原理即通过拦截器识别查询语句,当为查询语句且配置了 startPage() 则会自动为语句拼接 limit 查询。同时 PageHelper.startPage() 基于 ThreadLocal 实现,即每次查询后都将清空 startPage() 配置的值,因此每次分页查询前都需配置 startPage() 属性。

import com.github.pagehelper.PageInfo;

@Service("sysUserService")
public class SysUserServiceImpl implements SysUserService {
    @Override
    public PageInfo<SysUser> queryByPageHelper(SysUser sysUser, PageDetails pageDetails) {
        // 获取分页参数
        int offset = pageDetails.getOffset();
        int limit = pageDetails.getLimit();
        // 开启分页查询,执行查询后失效
        PageHelper.startPage(offset, limit);
        List<SysUser> list = this.sysUserDao.list(sysUser);
        return new PageInfo<>(list);
    }
}

默认 PageHelper.startPage() 在执行分页查询时会执行 count 统计总数用于构建 PageInfo 对象,若不需要总行数统计可设置关闭,当设置为 falsePageInfo 实例的 count 属性为 -1

import com.github.pagehelper.PageInfo;

@Service("sysUserService")
public class SysUserServiceImpl implements SysUserService {
    @Override
    public PageInfo<SysUser> queryByPageHelper(SysUser sysUser, PageDetails pageDetails) {
        // 获取分页参数
        int offset = pageDetails.getOffset();
        int limit = pageDetails.getLimit();
        // 不执行 count 查询
        PageHelper.startPage(offset, limit, false);
        List<SysUser> list = this.sysUserDao.list(sysUser);
        return new PageInfo<>(list);
    }
}

如测试表 sys_user 中有 25 条数据,则当 offset=1,limit=3 执行分页查询后返回的数据格式如下:

{
  "total": 25,
  "list": [
    {
      "id": "1",
      "name": "Adel",
      "gender": "Male",
      "createTime": "2007-12-17T14:20:42.000+00:00"
    },
    {
      "id": "2",
      "name": "Tiffany",
      "gender": "Female",
      "createTime": "2018-11-10T11:49:46.000+00:00"
    },
    {
      "id": "3",
      "name": "Steve",
      "gender": "Male",
      "createTime": "1992-03-09T11:01:56.000+00:00"
    }
  ],
  "pageNum": 1,
  "pageSize": 3,
  "size": 3,
  "startRow": 1,
  "endRow": 3,
  "pages": 9,
  "prePage": 0,
  "nextPage": 2,
  "isFirstPage": true,
  "isLastPage": false,
  "hasPreviousPage": false,
  "hasNextPage": true,
  "navigatePages": 8,
  "navigatepageNums": [
    1,
    2,
    3,
    4,
    5,
    6,
    7,
    8
  ],
  "navigateFirstPage": 1,
  "navigateLastPage": 8
}

文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录