Mybatis 分页组件PageHelper 使用

PageHelper 官网:

  1. https://pagehelper.github.io
  2. 关于PageHelper 的开发和原理官网上也已经讲的很明确了,这里不过多解析官网的意思

快速构建:

  1. 首先需要一个 SSM 项目【也可以单体Mybatis】
  2. 新增Maven 配置:
<dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper</artifactId>
        <version>5.1.10</version>
      </dependency>
  1. mybatis-config.xml 加入以下配置【也可以写在Spring的配置中,写法略有不同】:
<configuration>
    <!--Mysql 配置驼峰命名-->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!--分页插件-->
    <plugins>
        <!-- com.github.pagehelper为PageHelper类所在包名 -->
        <plugin interceptor="com.github.pagehelper.PageInterceptor" >
        </plugin>
    </plugins>

</configuration>
  1. Dao 以及 Xml 写法。
// Dao
List<ClassInfo> getListByPage();
<!-- XML -->
<select id="getListByPage" parameterType="com.github.pagehelper.PageInfo" resultType="com.morning.all.entity.test.ClassInfo">
        select * from  tb_class
    </select>
  1. 重点Spring 的实现类:
/**
     * 分页查询数据
     * @return
     */
    public PageInfo getListByPage(PageInfo pageInfo){
        //设置分页信息(第几页,每页数量),并将Page 对象放入到 ThreadLocal 中,
        PageHelper.startPage(pageInfo.getPageNum(), pageInfo.getPageSize());
        //查询结果,我们的拦截器会在这里起作用
        List<ClassInfo> result = classDao.getListByPage();
        // 生成返回结果
        PageInfo<ClassInfo> pageInfoResult = new PageInfo<ClassInfo>(result);
        return pageInfoResult;
    }

这里需要注意的是,getListByPage 的参数PageInfo 仅仅由于接受 前端分页参数,不可用于返回。

  1. 通过Controller执行我们的代码,看一下Mybatis sql打印结果:
    在这里插入图片描述
    哦吼,为什么会打印出这两条SQL ,一条Count,一条Select,我原来的Sql 为什么没有执行那,不着急我们看下一部分

源码浅析:

  1. 要想真正理解这个功能,需要对Mybatis源码 和 Mybatis拦截器有一定的了解,那么PageInterceptor中究竟做了什么那,直接上源码,因为它是中国人开发的,代码注释也一目了然,完全Copy
package com.github.pagehelper;
public class PageInterceptor implements Interceptor {
    private volatile Dialect dialect;
    private String countSuffix = "_COUNT";
    protected Cache<String, MappedStatement> msCountMap = null;
    private String default_dialect_class = "com.github.pagehelper.PageHelper";

    @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 个参数时
                boundSql = ms.getBoundSql(parameter);
                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);
                    }
                }
                resultList = ExecutorUtil.pageQuery(dialect, executor,
                        ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
            } else {
                //rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
                resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
            }
            return dialect.afterPage(resultList, parameter, rowBounds);
        } finally {
            if(dialect != null){
                dialect.afterAll();
            }
        }
    }

    /**
     * Spring bean 方式配置时,如果没有配置属性就不会执行下面的 setProperties 方法,就不会初始化
     * <p>
     * 因此这里会出现 null 的情况 fixed #26
     */
    private void checkDialectExists() {
        if (dialect == null) {
            synchronized (default_dialect_class) {
                if (dialect == null) {
                    setProperties(new Properties());
                }
            }
        }
    }

    private Long count(Executor executor, MappedStatement ms, Object parameter,
                       RowBounds rowBounds, ResultHandler resultHandler,
                       BoundSql boundSql) throws SQLException {
        String countMsId = ms.getId() + countSuffix;
        Long count;
        //先判断是否存在手写的 count 查询
        MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);
        if (countMs != null) {
            count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
        } else {
            countMs = msCountMap.get(countMsId);
            //自动创建
            if (countMs == null) {
                //根据当前的 ms 创建一个返回值为 Long 类型的 ms
                countMs = MSUtils.newCountMappedStatement(ms, countMsId);
                msCountMap.put(countMsId, countMs);
            }
            count = ExecutorUtil.executeAutoCount(dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler);
        }
        return count;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        //缓存 count ms
        msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);
        String dialectClass = properties.getProperty("dialect");
        if (StringUtil.isEmpty(dialectClass)) {
            dialectClass = default_dialect_class;
        }
        try {
            Class<?> aClass = Class.forName(dialectClass);
            dialect = (Dialect) aClass.newInstance();
        } catch (Exception e) {
            throw new PageException(e);
        }
        dialect.setProperties(properties);

        String countSuffix = properties.getProperty("countSuffix");
        if (StringUtil.isNotEmpty(countSuffix)) {
            this.countSuffix = countSuffix;
        }
    }

}

  1. Mybatis 执行 Dao层方法时执行时,会被拦截器拦截执行Interceptor.intercept()方法,我们可以再源码中看到 第37行时查询了总条数,在第44行执行了 分页查询,也就是说PageInterceptor会拦截请求,找到我们本来要执行的Sql并将其拆解,生成 count 和 Select Limit Sql 执行,并最后将结果返回。
  2. 那么为什么我们原来的XML 配置的sql 并没有执行那,是因为Mybatis执行XML中的sql 也是一个拦截器,Mybatis执行拦截器是责任链的形式,而Mybatis自己的拦截器优先级最低,先执行了 PageInterceptor,并且PageInterceptor 中没有调用 Mybatis本身的拦截器,所以就没有执行。需要在PageInterceptor.intercept() 中加入代码: Object result = invocation.proceed(); 程序才会回去执行下一级拦截器,显然这里根本不需要执行下一个拦截器
以上仅为博主的粗浅认识,如有疑问或建议,可以留言多多探讨,博主会第一时间更正。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值