Mybatis-PageHelper分页插件的使用与相关原理分析

简介: 今天使用了分页插件,并将其整合到SpringBoot中。各种遇到了个别问题,现在记录下。吃一垫长一智

前言

今天使用了分页插件,并将其整合到SpringBoot中。各种遇到了个别问题,现在记录下。吃一垫长一智。

整合

与SpringBoot整合

1. 引入依赖

 

<!--pagehelper 分页插件-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.5</version>
        </dependency>

2. 配置参数

接着在application.yml中配置相关参数

#pagehelper
pagehelper:
    helperDialect: mysql
    reasonable: true
    supportMethodsArguments: true
    params: count=countSql
    returnPageInfo: check

参数说明

https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md


3.使用

#方式1
  PageHelper.startPage(1, 10);
   List<ScoreGoodsCategory> goodsCategoryList = mapper.selectByPage();
   int totalCount=(int) ((Page)goodsCategoryList).getTotal();
   # 方式二
   PageHelper.offsetPage(1, 10);
   List<ScoreGoodsCategory> goodsCategoryList = mapper.selectByPage();
   PageInfo<ScoreGoodsCategory> pageInfo = new PageInfo<>(goodsCategoryList);
   int totalCount=(int) pageInfo.getTotal();


与Spring MVC 整合

1. 引入依赖

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>最新版本</version>
</dependency>

2. 配置拦截器(这是核心,如果不配置则分页不起作用)

在Spring的配置文件中配置拦截器插件

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <!-- 注意其他配置 -->
  <property name="plugins">
    <array>
      <bean class="com.github.pagehelper.PageInterceptor">
        <property name="properties">
          <!--使用下面的方式配置参数,一行配置一个 -->
          <value>
            params=count=countSql
          </value>
     <value>
            helperDialect=mysql
          </value>
     <value>
            reasonable=true
          </value>
      <value>
            supportMethodsArguments=true
          </value>
       <value>
            returnPageInfo=check
          </value>
        </property>
      </bean>
    </array>
  </property>
</bean>

配置好之后,使用同上。


原理

其最核心的方法就在拦截器中,那我们首先看看拦截器中的拦截方法。该方法主要做了两件事,1. 统计总条数,2.对原始的SQL进行改写使其可以分页。

PageInterceptor类的intercept方法是拦截器的总入口方法。

1.统计总条数

首先,我们来看看统计总条数的相关代码。

//PageInterceptor 类
//设置count的sql的id,在原始的msId后面加上_COUNT后缀。
 String countMsId = msId + countSuffix;
 // 生成统计sql的入口方法
 count = executeAutoCount(executor, countMs, parameter, boundSql, rowBounds, resultHandler);

接下来我们就来看看executeAutoCount 方法


/**
     * 执行自动生成的 count 查询
     *
     * @param executor sql执行器
     * @param countMs
     * @param parameter
     * @param boundSql 
     * @param rowBounds  
     * @param resultHandler  结果处理器
     * @return
     * @throws IllegalAccessException
     * @throws SQLException
     */
    private Long executeAutoCount(Executor executor, MappedStatement countMs,
                                   Object parameter, BoundSql boundSql,
                                   RowBounds rowBounds, ResultHandler resultHandler) throws IllegalAccessException, SQLException {
        Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql);
        //创建 count 查询的缓存 key
        CacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);
        //调用方言获取 count sql
        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));
        }
        //执行 count 查询
        Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
        Long count = (Long) ((List) countResultList).get(0);
        return count;
    }

如上,方法的注释比较详实,此处我们主要介绍下第二步调用方言获取 count sql。dialect 是一个接口类,PageHelper是其的一个实现类。接着我们来看看PageHelper中的getCountSql方法。

//*PageHelper
    @Override
    public String getCountSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey) {
        return autoDialect.getDelegate().getCountSql(ms, boundSql, parameterObject, rowBounds, countKey);
    }

如上,在PageHelper的getCountSql直接把请求给了AbstractHelperDialect(通过autoDialect.getDelegate() 获得) 的getCountSql 方法。我们接着往下看

//*AbstractHelperDialect
    @Override
    public String getCountSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey) {
        Page<Object> page = getLocalPage();
        String countColumn = page.getCountColumn();
        if (StringUtil.isNotEmpty(countColumn)) {
            return countSqlParser.getSmartCountSql(boundSql.getSql(), countColumn);
        }
        return countSqlParser.getSmartCountSql(boundSql.getSql());
    }

如上,countsql语句的生成逻辑最终落在了CountSqlParser类,该类是一个通用的sql解析类。最后我们来看看CountSqlParser类的getSmartCountSql方法。

/**
     * 获取智能的countSql
     *
     * @param sql 我们传入的需要分页的sql
     * @param name 列名,默认 0
     * @return
     */
    public String getSmartCountSql(String sql, String name) {
        //解析SQL
        Statement stmt = null;
        //特殊sql不需要去掉order by时,使用注释前缀
        if(sql.indexOf(KEEP_ORDERBY) >= 0){
            return getSimpleCountSql(sql);
        }
        try {
  // 对sql 进行分解
            stmt = CCJSqlParserUtil.parse(sql);
        } catch (Throwable e) {
            //无法解析的用一般方法返回count语句
            return getSimpleCountSql(sql);
        }
        Select select = (Select) stmt;
        SelectBody selectBody = select.getSelectBody();
        try {
            //处理body-去order by
            processSelectBody(selectBody);
        } catch (Exception e) {
            //当 sql 包含 group by 时,不去除 order by
            return getSimpleCountSql(sql);
        }
        //处理with-去order by
        processWithItemsList(select.getWithItemsList());
        //处理为count查询
        sqlToCount(select, name);
        String result = select.toString();
        return result;
    }

调试结果:

大致流程如下(举例说明):

1.原始sql: Select id, name FROM student WHERE sex=?

2.分解sql存入SelectBody的实现类PlainSelect中,主要的部分是selectItems,fromItem,where

3.然后就是将selectItems替换成count(0)

4.最后在组装成sql返回,组装后的sql是Select count(0) FROM student WHERE sex=?

countsql的生成逻辑说完之后,接下来我们看看分页过程。

2. 对sql进行分页

对sql 进行分页的入口逻辑还是在PageInterceptor类的intercept方法中。话不多说,上代码。

//生成分页的缓存 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(configuration, pageSql, boundSql.getParameterMappings(), parameter);
                    //设置动态参数
                    for (String key : additionalParameters.keySet()) {
                        pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
                    }
                    //执行分页查询
                    resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);

如上,主要四步:

1.处理参数对象

2.获取待分页的sql,

3.设置动态参数,

4.执行分页查询。

部分问题处理(踩坑)

坑一、在查出数据集合list之后对list做了处理,例如:

List<String> taxNoList = juhePayClearingRecordMapper.pageClearingTaxNo(tradeTime);
        if (taxNoList == null) {
            return null;
        }
        taxNoList = taxNoList.stream().filter(b -> StringUtils.isNotBlank(b)).collect(Collectors.toList());
        PageInfo<String> pageInfo = new PageInfo<>(taxNoList);
        PagePOJO<String> taxNoInfo = new PagePOJO<>();
        taxNoInfo.setACount((int) pageInfo.getTotal());

如上,先通过SQL语句插入集合taxNoList,然后对taxNoList做了一个去除空字符串的处理,再获取总条数时,数据不对

这是因为查询出来的taxNoList,实际上是一个Page对象,所以获取总条数是调用的((Page)list).getTotal()来获取的。而如果对taxNoList进行处理之后,他就变成了一个普通的ArrayList对象了。所以,获取的总条数total不对。

da2499b6b00b316ffbe0347452c56541_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.png

2. 坑二、在PageHelper.startPage 方法之后添加了代码,同样的会导致不能分页

0238e9616e466e4acaba89fe5b857adb_20200730175931122.png

总结

首先感谢liuzh同志开发出了这款好用的插件,代码很规范,插件很好用。本文首先介绍了Mybatis-PageHelper插件的整合与使用,接着介绍了相关原理,主要是统计总条数的实现原理。希望对读者朋友们有所帮助。


https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md


相关文章
|
2月前
|
SQL Java 数据库连接
深入 MyBatis-Plus 插件:解锁高级数据库功能
Mybatis-Plus 提供了丰富的插件机制,这些插件可以帮助开发者更方便地扩展 Mybatis 的功能,提升开发效率、优化性能和实现一些常用的功能。
311 26
深入 MyBatis-Plus 插件:解锁高级数据库功能
|
2月前
|
SQL Java 数据库连接
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
MyBatis-Plus是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。本文讲解了最新版MP的使用教程,包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段等核心功能。
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
|
2月前
|
SQL Java 数据库连接
Mybatis架构原理和机制,图文详解版,超详细!
MyBatis 是 Java 生态中非常著名的一款 ORM 框架,在一线互联网大厂中应用广泛,Mybatis已经成为了一个必会框架。本文详细解析了MyBatis的架构原理与机制,帮助读者全面提升对MyBatis的理解和应用能力。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Mybatis架构原理和机制,图文详解版,超详细!
|
2月前
|
SQL 缓存 Java
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
|
3月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
94 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
3月前
|
SQL XML Java
Mybatis的原理和MybaitsPlus
这篇文章对比分析了Mybatis和Mybatis Plus的特点与底层实现机制,探讨了两者之间的差异及各自的优势。
105 0
|
3月前
|
Java 数据库连接 Maven
mybatis使用一:springboot整合mybatis、mybatis generator,使用逆向工程生成java代码。
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和MyBatis Generator,使用逆向工程来自动生成Java代码,包括实体类、Mapper文件和Example文件,以提高开发效率。
165 2
mybatis使用一:springboot整合mybatis、mybatis generator,使用逆向工程生成java代码。
|
3月前
|
前端开发 Java Apache
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个
本文详细讲解了如何整合Apache Shiro与Spring Boot项目,包括数据库准备、项目配置、实体类、Mapper、Service、Controller的创建和配置,以及Shiro的配置和使用。
656 1
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个
|
3月前
|
SQL Java 数据库连接
mybatis使用二:springboot 整合 mybatis,创建开发环境
这篇文章介绍了如何在SpringBoot项目中整合Mybatis和MybatisGenerator,包括添加依赖、配置数据源、修改启动主类、编写Java代码,以及使用Postman进行接口测试。
43 0
mybatis使用二:springboot 整合 mybatis,创建开发环境
|
3月前
|
Java 数据库连接 API
springBoot:后端解决跨域&Mybatis-Plus&SwaggerUI&代码生成器 (四)
本文介绍了后端解决跨域问题的方法及Mybatis-Plus的配置与使用。首先通过创建`CorsConfig`类并设置相关参数来实现跨域请求处理。接着,详细描述了如何引入Mybatis-Plus插件,包括配置`MybatisPlusConfig`类、定义Mapper接口以及Service层。此外,还展示了如何配置分页查询功能,并引入SwaggerUI进行API文档生成。最后,提供了代码生成器的配置示例,帮助快速生成项目所需的基础代码。
209 1