当前位置: 首页 > news >正文

PageHelper分页插件

文章目录

    • 1、使用方式
    • 2、原理
    • 3、注意事项

1、使用方式

引入 PageHelper 插件

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.11</version>
</dependency>

mybatis-config.xml 中添加插件配置

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <property name="dialect" value="mysql"/>
    </plugin>
</plugins>

在查询方法中使用 PageHelper

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;

public List<YourEntity> selectPage(int pageNum, int pageSize) {
    // 开启分页
    PageHelper.startPage(pageNum, pageSize);
    // 执行查询
    List<YourEntity> list = yourMapper.selectAll();
    // 通过 PageInfo 封装分页信息
    PageInfo<YourEntity> pageInfo = new PageInfo<>(list);
    return list;
}

2、原理

我们可以看到,使用pageHelper插件时使用主要分三步:( 1.开启分页、2.执行查询、3.处理结果 ),那么原理是怎么样的呢?

PageHelper.startPage(pageNum, pageSize);

开启分页后,最终会调用如下重载方法

public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
    Page<E> page = new Page<E>(pageNum, pageSize, count);
    page.setReasonable(reasonable);
    page.setPageSizeZero(pageSizeZero);
    //当已经执行过orderBy的时候
    Page<E> oldPage = getLocalPage();
    if (oldPage != null && oldPage.isOrderByOnly()) {
        page.setOrderBy(oldPage.getOrderBy());
    }
    setLocalPage(page);
    return page;
}

它存在一个关键动作 setLocalPage,将页面对象存储到 ThreadLocal 当中。

    protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
    /**
     * 设置 Page 参数
     *
     * @param page
     */
    protected static void setLocalPage(Page page) {
        LOCAL_PAGE.set(page);
    }

在执行查询时会被 PageInterceptor 拦截器拦截,拦截方法为 intercept

public Object intercept(Invocation invocation) throws Throwable {
    try {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        ResultHandler resultHandler = (ResultHandler) args[3];
        Executor executor = (Executor) invocation.getTarget();
        CacheKey cacheKey;
        BoundSql boundSql;
        //由于逻辑关系,只会进入一次
        if (args.length == 4) {
            //4 个参数时
            boundSql = ms.getBoundSql(parameter); // 拦截原始 sql
            cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
        } else {
            //6 个参数时
            cacheKey = (CacheKey) args[4];
            boundSql = (BoundSql) args[5];
        }
        checkDialectExists();
        List resultList;
        //调用方法判断是否需要进行分页,如果不需要,直接返回结果
        if (!dialect.skip(ms, parameter, rowBounds)) {
            //判断是否需要进行 count 查询
            if (dialect.beforeCount(ms, parameter, rowBounds)) {
                //查询总数
                Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                //处理查询总数,返回 true 时继续分页查询,false 时直接返回
                if (!dialect.afterCount(count, parameter, rowBounds)) {
                    //当查询总数为 0 时,直接返回空的结果
                    return dialect.afterPage(new ArrayList(), parameter, rowBounds);
                }
            }
            // 进行分页查询,并将原始sql作为参数传递到此方法中
            resultList = ExecutorUtil.pageQuery(dialect, executor,
                    ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
        } else {
            //rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
            resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
        }
        // 分页查询后,处理分页结果,拦截器中直接 return 该方法的返回值
        return dialect.afterPage(resultList, parameter, rowBounds);
    } finally {
        if(dialect != null){
            // 查询的后置操作,清除 ThreadLocal 本地变量
            dialect.afterAll();
        }
    }
}

执行分页查询方法

public static  <E> List<E> pageQuery(Dialect dialect, Executor executor, MappedStatement ms, Object parameter,
                             RowBounds rowBounds, ResultHandler resultHandler,
                             BoundSql boundSql, CacheKey cacheKey) throws SQLException {
    //判断是否需要进行分页查询
    if (dialect.beforePage(ms, parameter, rowBounds)) {
        //生成分页的缓存 key
        CacheKey pageKey = cacheKey;
        //处理参数对象
        parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
        //调用方言获取分页 sql
        String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
        BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);
        Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
        //设置动态参数
        for (String key : additionalParameters.keySet()) {
            pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
        }
        //执行分页查询
        return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
    } else {
        //不执行分页的情况下,也不执行内存分页
        return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
    }
}

执行分页查询方法时,还会调用 AbstractHelperDialect 的 getPageSql 方法获取分页 sql

public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
    String sql = boundSql.getSql();
    // 从ThreadLocal中获取分页对象
    Page page = getLocalPage();
    //支持 order by
    String orderBy = page.getOrderBy();
    if (StringUtil.isNotEmpty(orderBy)) {
        pageKey.update(orderBy);
        sql = OrderByParser.converToOrderBySql(sql, orderBy);
    }
    if (page.isOrderByOnly()) {
        return sql;
    }
    // 当前类为抽象类,会调用具体方言的 getPageSql 方法,如 mysql
    return getPageSql(sql, page, pageKey);
}

分页查询执行完成后会执行 方法,处理结果

public Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds) {
    Page page = getLocalPage();
    if (page == null) {
        return pageList;
    }
    page.addAll(pageList);
    if (!page.isCount()) {
        page.setTotal(-1);
    } else if ((page.getPageSizeZero() != null && page.getPageSizeZero()) && page.getPageSize() == 0) {
        page.setTotal(pageList.size());
    } else if(page.isOrderByOnly()){
        page.setTotal(pageList.size());
    }
    return page;
}

分页查询结果处理完成后会执行 afterAll() 清除 ThreadLocal 中设置的分页参数

public void afterAll() {
    //这个方法即使不分页也会被执行,所以要判断 null
    AbstractHelperDialect delegate = autoDialect.getDelegate();
    if (delegate != null) {
        delegate.afterAll();
        autoDialect.clearDelegate();
    }
    // 清除分页对象
    clearPage();
}
public static void clearPage() {
    LOCAL_PAGE.remove();
}

3、注意事项

由于我们执行一次分页查询后就会清空 ThreadLocal 中的 Page 对象,所以如果一个方法中如果要进行两次分页查询就需要设置两次分页参数

    // 开启分页
    PageHelper.startPage(pageNum, pageSize);
    // 执行查询
    List<YourEntity> list = yourMapper.selectAll();
    // 开启分页
    PageHelper.startPage(pageNum, pageSize);
    // 执行查询
    List<YourEntity> list = yourMapper.selectAll();

另外,执行分页查询前会先执行一次 count 查询,这样似乎有利于性能提升、避免无效查询,同时还可以获得总记录数。

如果不想执行 count 查询,创建 Page 对象时可以设置 count 变量为 false

public class Page<E> extends ArrayList<E> implements Closeable {
    private static final long serialVersionUID = 1L;

    /**
     * 页码,从1开始
     */
    private int pageNum;
    /**
     * 页面大小
     */
    private int pageSize;
    /**
     * 起始行
     */
    private int startRow;
    /**
     * 末行
     */
    private int endRow;
    /**
     * 总数
     */
    private long total;
    /**
     * 总页数
     */
    private int pages;
    /**
     * 包含count查询
     */
    private boolean count = true;
    /**
     * 分页合理化
     */
    private Boolean reasonable;
    /**
     * 当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果
     */
    private Boolean pageSizeZero;
    /**
     * 进行count查询的列名
     */
    private String countColumn;
    /**
     * 排序
     */
    private String orderBy;
    /**
     * 只增加排序
     */
    private boolean orderByOnly;
}

PageHelper 的使用和原理非常简单,平时注意即可。

相关文章:

  • C语言题目:链表数据求和操作
  • 【系列教程】Python第三课:用前两课知识解决实际问题
  • “mysqld --initialize --console ”执行不成功情况总结和解决措施
  • vue3-04vue3中ref函数( 定义一个响应式的数据)
  • 设计模式14:职责链模式
  • 普通报表入门
  • 使用html css js 开发一个 教育机构前端静态网站模板
  • Show 『Picture Add + Crosee Line ROI
  • 【ISO 14229-1:2023 UDS诊断(ECU复位0x11服务)测试用例CAPL代码全解析④】
  • 《StyleID:一种无训练的方法将大规模扩散模型适配于风格迁移》
  • Mistral Saba:为中东和南亚量身打造的AI模型
  • npu 瑞芯微rk系列,rknn模型转换以及npu使用
  • ES8字符串填充用法总结:padStart(),padEnd(),rest剩余参数的用法{name,...obj},扩展运算符的用法,正则表达式命名捕获组
  • 聚焦地灾防治,助力城市地质安全风险防控
  • 基于STM32的智能交通信号控制系统
  • Windows环境打印文档的同时自动生成PDF副本的方法
  • SpringBoot中自动装配机制的原理
  • Pytorch实现论文之一种基于扰动卷积层和梯度归一化的生成对抗网络
  • 2024年GESP09月认证Scratch一级试卷
  • 问题定位总结
  • 从能源装备向应急装备蓝海拓展,川润股份发布智能综合防灾应急仓
  • 秦洪看盘|指标股发力,A股渐有突破态势
  • 西安市未央区委书记刘国荣已任西咸新区党工委书记
  • 海运港口股掀涨停潮!回应关税下调利好,有货代称美线舱位爆了
  • 地下5300米开辟“人造气路”,我国页岩气井垂深纪录再刷新
  • 专访|韩国世宗研究所中国研究中心主任:李在明若上台将推行均衡外交