PageHelper是怎么分页的

1、前言

PageHelper是mybatis 提供的分页插件,通过PageHelper.startPage(pageNo,pageLimit)就可以帮我们实现分页,目前支持Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六种数据库。

pom依赖:

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

yml配置:

pagehelper:
  helperDialect: mysql #数据库类型,不指定的话会解析 datasource.url进行配置
  supportMethodsArguments: true
  params: count=countSql 

使用示例:

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;
 /**
     * 依据用户昵称进行模糊分页查询
     *
     * @param name
     * @param page
     * @param limit
     * @return
     */
    public PageInfo<User> findPageUsersByName(String name, int page, int limit) {
        PageHelper.startPage(page, limit);
        List<User> users = userMapper.selectByName(name);
        PageInfo<User> pageUsers = new PageInfo<>(users);
        return pageUsers;
    }
}

2、基本流程

1、PageHelper向 Mybatis 注册处理分页和 count 的拦截器 PageInterceptor

2、通过 PageHelper.startPage() 方法把分页相关的参数放到 ThreadLcoal 中

3、Mybatis 执行 SQL 过程中会调用拦截器

  • 3.1、根据查询 SQL 构建 count SQL
  • 3.2、从 ThreadLcoal 拿出分页信息,在查询 SQL 后面拼接 limit ?, ?
  • 3.3、清空 ThreadLcoal

4、 使用 Page 创建 PageInfo 对象

3、源码分析

3.1、存储分页参数

直接看PageHelper.startPage,startPage() 方法会构建一个 Page 对象,存储分页相关的参数、设置,最后调用 setLocalPage(page),将其放入ThreadLocal。由于ThreadLocal 每次查询后都会被remove掉,所以一次mapper查询对应一次PageHelper.startPage。

ThreadLocal原理可以看这篇ThreadLocal源码及其内存泄漏问题

    public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
        Page<E> page = new Page(pageNum, pageSize, count);
        page.setReasonable(reasonable);
        page.setPageSizeZero(pageSizeZero);
        Page<E> oldPage = getLocalPage();
        if (oldPage != null && oldPage.isOrderByOnly()) {
            page.setOrderBy(oldPage.getOrderBy());
        }

        setLocalPage(page);
        return page;
    }

3.2、改造SQL

PageInterceptor 实现了 org.apache.ibatis.plugin.Interceptor 接口,MyBatis 底层查询其实就是借助SqlSession调用Executor#query,mybatis 在执行查询方法的时候(method = “query”)会调用本拦截器。

/**
 * Mybatis - 通用分页拦截器
 */
@SuppressWarnings({"rawtypes", "unchecked"})
@Intercepts(
        {
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        }
)
public class PageInterceptor implements Interceptor {
    // 默认为 Pagehelper
    private volatile Dialect dialect;
    // count 方法的后缀
    private String countSuffix = "_COUNT";
    //  count 查询的缓存,只用于
    // 本例中 key 为 com.example.pagehelper.dao.UserMapper.selectUsers_COUNT
    protected Cache<String, MappedStatement> msCountMap = null;
    // 
    private String default_dialect_class = "com.github.pagehelper.PageHelper";
    ...
}    

进入PageInterceptor.intercept()方法,

@Override
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 个参数时
            
            // 拿到原始的查询 SQL
            boundSql = ms.getBoundSql(parameter);
            cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
        } else {
            //6 个参数时
            cacheKey = (CacheKey) args[4];
            boundSql = (BoundSql) args[5];
        }
        checkDialectExists();
        //对 boundSql 的拦截处理
        // 实际什么都没做,原样返回了
        if (dialect instanceof BoundSqlInterceptor.Chain) {
            boundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.ORIGINAL, boundSql, cacheKey);
        }
        List resultList;
        //调用方法判断是否需要进行分页,如果不需要,直接返回结果
        if (!dialect.skip(ms, parameter, rowBounds)) {
            //判断是否需要进行 count 查询
            if (dialect.beforeCount(ms, parameter, rowBounds)) {
                // 查询总数
                // 见 PageInterceptor.count()
                Long count = count(executor, ms, parameter, rowBounds, null, boundSql);
                //处理查询总数,返回 true 时继续分页查询,false 时直接返回
                if (!dialect.afterCount(count, parameter, rowBounds)) {
                    //当查询总数为 0 时,直接返回空的结果
                    return dialect.afterPage(new ArrayList(), parameter, rowBounds);
                }
            }
            // 执行分页查询
            resultList = ExecutorUtil.pageQuery(dialect, executor,
                    ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
        } else {
            //rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
            resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
        }
        // 将count、分页 信息放入 ThreadLocal
        return dialect.afterPage(resultList, parameter, rowBounds);
    } finally {
        if(dialect != null){
            dialect.afterAll();
        }
    }
}

count
PageInterceptor.count()

private Long count(Executor executor, MappedStatement ms, Object parameter,
                    RowBounds rowBounds, ResultHandler resultHandler,
                    BoundSql boundSql) throws SQLException {
    // countMsId = "com.example.pagehelper.dao.UserMapper.selectUsers_COUNT"
    String countMsId = ms.getId() + countSuffix;
    Long count;
    //先判断是否存在手写的 count 查询
    MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);
    if (countMs != null) {
        // 直接执行手写的 count 查询
        count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
    } else {
        // 先从缓存中查
        if (msCountMap != null) {
            countMs = msCountMap.get(countMsId);
        }
        // 缓存中没有,然后自动创建,并放入缓存
        if (countMs == null) {
            //根据当前的 ms 创建一个返回值为 Long 类型的 ms
            countMs = MSUtils.newCountMappedStatement(ms, countMsId);
            if (msCountMap != null) {
                // 放入缓存
                msCountMap.put(countMsId, countMs);
            }
        }
        // 执行 count 查询
        count = ExecutorUtil.executeAutoCount(this.dialect, executor, countMs, parameter, 
                                              boundSql, rowBounds, resultHandler);
    }
    return count;
}

ExecutorUtil.executeAutoCount()

public static Long executeAutoCount(Dialect dialect, Executor executor, MappedStatement countMs,
                                    Object parameter, BoundSql boundSql,
                                    RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
    //创建 count 查询的缓存 key
    CacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);
    //调用方言获取 count sql:SELECT count(0) FROM user
    // 见 PageHelper.getCountSql()
    String countSql = dialect.getCountSql(countMs, boundSql, parameter, rowBounds, countKey);
    //countKey.update(countSql);
    BoundSql countBoundSql = new BoundSql(countMs.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
    //当使用动态 SQL 时,可能会产生临时的参数,这些参数需要手动设置到新的 BoundSql 中
    for (String key : additionalParameters.keySet()) {
        countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
    }
    //对 boundSql 的拦截处理
    if (dialect instanceof BoundSqlInterceptor.Chain) {
        countBoundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.COUNT_SQL, countBoundSql, countKey);
    }
    //执行 count 查询
    Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
    Long count = (Long) ((List) countResultList).get(0);
    return count;
}

3.3、分页查询

ExecutorUtil.pageQuery

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,这里是重点,是添加 limit 的地方
        // pageSql = select id, name from user LIMIT ?, ? 
        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));
        }
        //对 boundSql 的拦截处理
        if (dialect instanceof BoundSqlInterceptor.Chain) {
            pageBoundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.PAGE_SQL, pageBoundSql, pageKey);
        }
        //执行分页查询
        return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
    } else {
        //不执行分页的情况下,也不执行内存分页
        return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
    }
}

3.4、使用 Page 创建 PageInfo 对象

最后使用PageInfo<User> pageUsers = new PageInfo<>(users)获取到分页信息
在这里插入图片描述

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
PageHelper排序分页是一种在数据库查询中使用的工具,它可以帮助我们实现按照指定条件进行排序和分页的功能。根据实现的方式不同,可以分为客户端排序分页和服务端排序分页。 客户端排序分页是指前端直接返回默认排序的全部数据,然后由前端组件来进行排序和分页。这种方式比较灵活,适用于数据量较少的情况。 服务端排序分页是指后端在数据库中执行查询,并利用数据库的特性进行分页,比如使用MySQL的limit和offset进行分页。为了使用PageHelper插件,我们需要在项目的pom文件中导入相关的依赖包,并在配置文件中进行相应的配置。 具体使用PageHelper的步骤如下: 1. 在pom文件中导入PageHelper的依赖包。 2. 在application.properties(或application.yml)文件中配置PageHelper的相关属性,比如数据库方言、是否合理化查询、是否支持方法参数等。 3. 在需要使用排序分页的查询方法上添加PageHelper的支持。 通过以上步骤,我们就可以在查询方法中使用PageHelper来设置排序和分页的条件了。 总结起来,PageHelper排序分页是一种方便实现排序和分页功能的工具,根据具体需求可以选择客户端排序分页或服务端排序分页的方式来使用。123 #### 引用[.reference_title] - *1* *2* [MyBatis使用PageHelper排序分页](https://blog.csdn.net/nklinsirui/article/details/105040621)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] - *3* [使用PageHelper实现分页查询(详细)](https://blog.csdn.net/m0_48736673/article/details/124805124)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值