Mybatis分页插件PageHelper手写实现示例

目录
  • 引言
  • 编写我们的插件类
  • 上面有二个核心方法
  • 获取记录总数
  • 分页查询记录数
  • 如何获取前端传递过来的参数?
  • 总结

引言

PageHelper是一个非常好用的插件,以至于很想知道它底层是怎么实现的。至于MyBatis插件概念原理网上有很多,我不太喜欢去写一些概念性的东西,我比较喜欢自己动手实现的那种,话不多说,我们开干

搭建一个SpringBoot+MyBatis+MySql项目

编写我们的插件类

package com.example.demo.plugin;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
@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 MyPagePlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        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];
        }
        == 其实在这之上的代码都是拷贝PageHelper源码来的,下面才是重头戏,上面都是获取一些必要的参数==
        /**
        * 下面4行代码暂时只需要知道是用来传参数的就行,分页不是需要
        * 二个参数嘛 一个是当前页,一个是数量
        */
        Page page = ThreadLocalUtil.getPage();
        Map<String,Object> params = new HashMap<>();
        params.put("first_key",page.getPageNum());
        params.put("second_key",page.getPageSize());
        /**
        * 重点:获取数据库记录总数
        */
        //统计总数
        Long count = MyExecutorUtil.executeAutoCount(executor,ms,parameter,boundSql,rowBounds,resultHandler);
        /**
        * 重点:分页查询
        */
        List<Object> objects = MyExecutorUtil.pageQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey, params);
        return objects;
    }
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    @Override
    public void setProperties(Properties properties) {
    }
}

上面有二个核心方法

1:获取记录总数:

MyExecutorUtil.executeAutoCount(executor,ms,parameter,boundSql,rowBounds,resultHandler);

2:分页查询:

MyExecutorUtil.pageQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey,params);

获取记录总数

1:MyBatis源码底层会封装好我们在配置文件中写的每一条SQL语句,封装到一个SqlSource对象中去,在我们执行SQL的时候,会获取到这条SQL,然后封装到BoundSql这个对象中,所以在这里,我们既然能拿到BoundSql,那么也就意味着我们能拿到我们需要执行的那条Sql了

2:获取到了我们的sql之后,怎么查询总记录数呢??

其实很简单,改Sql语句不就好了,Pagehelper底层也是这么做的,但是PageHelper底层比我这个版本的复杂太多了,但是我们无非就是将原先的SQL转换成 -> SELECT COUNT(0) FROM TABLE,就这样,但是我这个人比较懒,而且昨天看这个源码实现头疼,所以在这里直接写死了,但是问题不大哈

3:改好了Sql之后是不是就完成了呢??

当然不是,改好了SQL当然是要去执行它了,如果就这样执行,还是会执行原先的SQL,但是你要知道这个有个很关键的东西,就是MappedStatement的id,如果不改这个id的话,即使你的Sql能够执行成功,那么返回的记录总数是个NULL,这里我想可能是因为ResultSetHandle的关系,因为这个id对应的还是我们之前的sql,也就是select * from student,那么必然有一个resultType的属性,也就是实体类映射,但是我们现在的sql是select count(0) from student,那么就对应不上了,也就是数据库查询出来的列与实体类对应不上,所以我们需要改变这个id。

//改变MapperStatement id的方法

      public static MappedStatement newCountMappedStatement(MappedStatement ms, String newMsId) {
              MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), newMsId, ms.getSqlSource(), ms.getSqlCommandType());
              builder.resource(ms.getResource());
              builder.fetchSize(ms.getFetchSize());
              builder.statementType(ms.getStatementType());
              builder.keyGenerator(ms.getKeyGenerator());
              if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
                  StringBuilder keyProperties = new StringBuilder();
                  for (String keyProperty : ms.getKeyProperties()) {
                      keyProperties.append(keyProperty).append(",");
                  }
                  keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
                  builder.keyProperty(keyProperties.toString());
              }
              builder.timeout(ms.getTimeout());
              builder.parameterMap(ms.getParameterMap());
              //count查询返回值int
              List<ResultMap> resultMaps = new ArrayList<ResultMap>();
              ResultMap resultMap = new ResultMap.Builder(ms.getConfiguration(), ms.getId(), Long.class, EMPTY_RESULTMAPPING).build();
              resultMaps.add(resultMap);
              builder.resultMaps(resultMaps);
              builder.resultSetType(ms.getResultSetType());
              builder.cache(ms.getCache());
              builder.flushCacheRequired(ms.isFlushCacheRequired());
              builder.useCache(ms.isUseCache());
              return builder.build();
          }

4:MyBatis的Sql是封装到BoundSql中去的,而原先的BoundSql中的Sql是我们配置文件中的,所以我们需要将select count(0) from student这条Sql语句封装到一个新的BoundSql中去

public static Long executeAutoCount(Executor executor, MappedStatement countMs,
                                       Object parameter, BoundSql boundSql,
                                       RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
       //改变MapperStatement中的ID属性
       String countMsId = countMs.getId() + "_COUNT";
       countMs = newCountMappedStatement(countMs,countMsId);
       //创建 count 查询的缓存 key
       CacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);
       //调用获取count的sql
       String countSql = "select count(0) from student";
       //重新封装BoundSql对象
       BoundSql countBoundSql = new BoundSql(countMs.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
       //执行 count 查询
       List&lt;Long&gt; countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
       Long count = countResultList.get(0);
       return count;
   }

5:最后调用executor.query方法就可以得到我们的记录总数了

分页查询记录数

  • 1:其实分页查询与查询记录总数的原理是一样的
  • 2:首先获取原先的BoundSql中的Sql,也就是我们在配置文件中的Sql,比如 select * from student
  • 3:然后获取我们的分页参数,也就是前端传递过来的pageNum和pageSize二个参数
  • 4:修改Sql,select * from student limit ?,?
  • 5:这次我们就不用修改MapperStatement中的ID了,因为查询出来的都是studnet,所以映射不用修改
  • 6:重新实例化一个BoundSql对象,将新的Sql传递给它
  • 7:最后执行executor.query方法查询,得到结果

如何获取前端传递过来的参数?

1:我这里使用的是ThreadLocal,

总结

  • 1:首选我们需要获取到原先需要执行的sql
  • 2:然后修改这条修改,分页查询无非就是总记录数,分页查询的记录数
  • 3:分别执行这二条Sql,得到我们想要的结果

源码:

就下面这5个类,其它的就是将SpringBoot+MyBatis+MySql日常配置就行了,然后再Controller记得传递一下参数

package com.example.demo.plugin;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan({"com.example.demo.mapper"})
public class MapperConfig {
    @Bean
    public MyPagePlugin myPagePlugin() {
        return new MyPagePlugin();
    }
}
package com.example.demo.plugin;
import com.example.demo.entity.Student;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class MyExecutorUtil {
    private static final List&lt;ResultMapping&gt; EMPTY_RESULTMAPPING = new ArrayList&lt;ResultMapping&gt;(0);
    public static  &lt;E&gt; List&lt;E&gt; pageQuery(Executor executor, MappedStatement ms, Object parameter,
                                         RowBounds rowBounds, ResultHandler resultHandler,
                                         BoundSql boundSql, CacheKey cacheKey,Map&lt;String,Object&gt; params) throws SQLException {
        executor.clearLocalCache();
        //生成分页的缓存 key
        CacheKey pageKey = cacheKey;
        //处理参数对象
        if(params.size() &lt; 0 || params.size() &gt; 2) {
            System.out.println("参数错误");
        }
        //获取sql
        String pageSql = getPageSql(params.size(),boundSql);
        List&lt;ParameterMapping&gt; mappingList = new ArrayList&lt;&gt;();
        mappingList.add(new ParameterMapping.Builder(ms.getConfiguration(), "first_key", Integer.class).build());
        mappingList.add(new ParameterMapping.Builder(ms.getConfiguration(), "second_key", Integer.class).build());
        //实例化新的BoundSql对象
        BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, mappingList, params);
        //执行分页查询
        return executor.query(ms, params, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
    }
    public static Long executeAutoCount(Executor executor, MappedStatement countMs,
                                        Object parameter, BoundSql boundSql,
                                        RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        //改变MapperStatement中的ID属性
        String countMsId = countMs.getId() + "_COUNT";
        countMs = newCountMappedStatement(countMs,countMsId);
        //创建 count 查询的缓存 key
        CacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);
        //调用获取count的sql
        String countSql = "select count(0) from student";
        //重新封装BoundSql对象
        BoundSql countBoundSql = new BoundSql(countMs.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
        //执行 count 查询
        List&lt;Long&gt; countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
        Long count = countResultList.get(0);
        return count;
    }
    public static MappedStatement newCountMappedStatement(MappedStatement ms, String newMsId) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), newMsId, ms.getSqlSource(), ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null &amp;&amp; ms.getKeyProperties().length != 0) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        //count查询返回值int
        List&lt;ResultMap&gt; resultMaps = new ArrayList&lt;ResultMap&gt;();
        ResultMap resultMap = new ResultMap.Builder(ms.getConfiguration(), ms.getId(), Long.class, EMPTY_RESULTMAPPING).build();
        resultMaps.add(resultMap);
        builder.resultMaps(resultMaps);
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }
    private static String getPageSql(int paramsLength,BoundSql boundSql){
        StringBuilder str = new StringBuilder();
        str.append(boundSql.getSql());
        if(paramsLength == 1) {
            str.append(" LIMIT ?");
        }else {
            str.append(" LIMIT ?,? ");
        }
        return str.toString();
    }
}
    package com.example.demo.plugin;
  import org.apache.ibatis.cache.CacheKey;
  import org.apache.ibatis.executor.Executor;
  import org.apache.ibatis.mapping.BoundSql;
  import org.apache.ibatis.mapping.MappedStatement;
  import org.apache.ibatis.plugin.*;
  import org.apache.ibatis.session.ResultHandler;
  import org.apache.ibatis.session.RowBounds;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Properties;
  @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 MyPagePlugin implements Interceptor {
      @Override
      public Object intercept(Invocation invocation) throws Throwable {
          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];
          }
          Page page = ThreadLocalUtil.getPage();
          Map&lt;String,Object&gt; params = new HashMap&lt;&gt;();
          params.put("first_key",page.getPageNum());
          params.put("second_key",page.getPageSize());
          //统计总数
          Long count = MyExecutorUtil.executeAutoCount(executor,ms,parameter,boundSql,rowBounds,resultHandler);
          System.out.println("count = " + count);
          List&lt;Object&gt; objects = MyExecutorUtil.pageQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey, params);
          return objects;
      }
      @Override
      public Object plugin(Object target) {
          return Plugin.wrap(target, this);
      }
      @Override
      public void setProperties(Properties properties) {
      }
  }
  package com.example.demo.plugin;
public class Page {
    private Integer pageNum;
    private Integer pageSize;
    public Integer getPageNum() {
        return pageNum;
    }
    public void setPageNum(Integer pageNum) {
        this.pageNum = pageNum;
    }
    public Integer getPageSize() {
        return pageSize;
    }
    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }
}
   package com.example.demo.plugin;
  public class ThreadLocalUtil {
      public static ThreadLocal&lt;Page&gt; PAGE_INFO = new ThreadLocal&lt;&gt;();
      public static void startPage(int pageNum,int pageSize) {
          Page page = new Page();
          page.setPageNum(pageNum);
          page.setPageSize(pageSize);
          PAGE_INFO.set(page);
      }
      public static Page getPage() {
          return PAGE_INFO.get();
      }
  }
  @RestController
  public class StudnetController {
      @Autowired
      private StudentMapper studentMapper;
      @GetMapping("/getData")
      public Object getData() {
          ThreadLocalUtil.startPage(0,2);
          return studentMapper.getData();
      }
  }

以上就是Mybatis分页插件PageHelper手写实现示例的详细内容,更多关于Mybatis分页插件PageHelper的资料请关注我们其它相关文章!

(0)

相关推荐

  • Mybatis分页插件PageHelper的配置和简单使用方法(推荐)

    前言 在web开发过程中涉及到表格时,例如dataTable,就会产生分页的需求,通常我们将分页方式分为两种:前端分页和后端分页. 前端分页 一次性请求数据表格中的所有记录(ajax),然后在前端缓存并且计算count和分页逻辑,一般前端组件(例如dataTable)会提供分页动作. 特点是:简单,很适合小规模的web平台:当数据量大的时候会产生性能问题,在查询和网络传输的时间会很长. 后端分页 在ajax请求中指定页码(pageNum)和每页的大小(pageSize),后端查询出当页的数据返回

  • SpringBoot+Mybatis分页插件PageHelper实现分页效果

    目录 一.项目结构 二.插件引入 三.代码 四.测试: 最近刚入职新公司,项目是从零开始搭建的项目.我觉得是时候考验是驴还是千里马的时候.都是泪就不多说了. 附上一篇Mybatis常用的分页案例.这次要做的是最常见的分页效果,也是基础功能.但是很多人都做不好的.这次采用Mybatis分页插件PageHelper.   仅献给伸手党的大爷们.大爷们好!拿代码记得扣666!!小的在这给感谢了!! 一.项目结构 按照controller,service,mapper(也叫dao)来建立项目,utils

  • Mybatis利用分页插件PageHelper快速实现分页查询

    目录 前言 首先创建一个Maven项目 数据库中创建一张表 设置Mybatis配置文件 编写pojo实体类和mapper接口和mapper映射文件 创建测试类 总结 前言 Mybatis算是对数据库操作的利器了.但是在处理分页的时候,Mybatis并没有什么特别的方法,一般需要自己去写limit子句实现,成本较高.好在有国内开发者写了一个PageHelper插件,可以帮助我们快速实现分页查询. 官网地址 首先创建一个Maven项目 导入相关依赖: <!-- 依赖列表--> <depend

  • MyBatis分页插件PageHelper的具体使用

    MyBatis分页插件PageHelper 如果你也在用 MyBatis,建议尝试该分页插件,这一定是最方便使用的分页插件.分页插件支持任何复杂的单表.多表分页. PageHelper是一个Mybatis的分页插件, 负责将已经写好的sql语句, 进行分页加工. PageHelper的使用 优点:无需你自己去封装以及关心sql分页等问题,使用很方便,前端取数据也很方便. 1.引入pagehelper依赖 <dependency> <groupId>com.github.pagehe

  • Mybatis pagehelper分页插件使用过程解析

    这篇文章主要介绍了mybatis pagehelper分页插件使用过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 使用过mybatis的人都知道,mybatis本身就很小且简单,sql写在xml里,统一管理和优化.缺点当然也有,比如我们使用过程中,要使用到分页,如果用最原始的方式的话,1.查询分页数据,2.获取分页长度,也就是说要使用到两个方法才能完成分页.有没有更更好的分页方式的,pagehelper分页插件因此而诞生,他的原理是利用

  • Mybatis分页插件PageHelper配置及使用方法详解

    环境 框架:spring+springmvc+mybatis pom.xml <!-- 引入mybatis的 pagehelper 分页插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.2</version> </dependency&g

  • Mybatis分页插件PageHelper手写实现示例

    目录 引言 编写我们的插件类 上面有二个核心方法 获取记录总数 分页查询记录数 如何获取前端传递过来的参数? 总结 引言 PageHelper是一个非常好用的插件,以至于很想知道它底层是怎么实现的.至于MyBatis插件概念原理网上有很多,我不太喜欢去写一些概念性的东西,我比较喜欢自己动手实现的那种,话不多说,我们开干 搭建一个SpringBoot+MyBatis+MySql项目 编写我们的插件类 package com.example.demo.plugin; import org.apach

  • 解决mybatis分页插件PageHelper导致自定义拦截器失效

    目录 问题背景 mybatis拦截器使用 使用方法: 注解参数介绍: setProperties方法 bug内容: 自定义拦截器部分代码 PageInterceptor源码: 解决方法: 解决方案一 调整执行顺序 解决方案二 修改拦截器注解定义 问题背景 在最近的项目开发中遇到一个需求 需要对mysql做一些慢查询.大结果集等异常指标进行收集监控,从运维角度并没有对mysql进行统一的指标搜集,所以需要通过代码层面对指标进行收集,我采用的方法是通过mybatis的Interceptor拦截器进行

  • mybatis分页插件pageHelper详解及简单实例

    mybatis分页插件pageHelper详解及简单实例 工作的框架spring springmvc mybatis3 首先使用分页插件必须先引入maven依赖,在pom.xml中添加如下 <!-- 分页助手 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>3.7.5

  • Mybatis分页插件PageHelper的使用详解

    1.说明 如果你也在用Mybatis,建议尝试该分页插件,这个一定是最方便使用的分页插件. 该插件目前支持Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六种数据库分页. 2.使用方法 第一步:在Mybatis配置xml中配置拦截器插件: <plugins> <!-- com.github.pagehelper为PageHelper类所在包名 --> <plugin interceptor="com.github.pageh

  • SSM使用mybatis分页插件pagehepler实现分页示例

    前几天在这里分享了手写 sql 分页查询实现分页,现在来看看使用 mybatis 分页插件 pagehepler 来实现分页 使用分页插件的原因,简化了sql代码的写法,实现较好的物理分页,比写一段完整的分页sql代码,也能减少了误差性. Mybatis分页插件 demo 项目地址:free-Mybatis_PageHelper_jb51.rar 我这里使用 maven 工程实现: 1.首先导入分页插件的依赖: <dependency> <groupId>com.github.pa

随机推荐