​

欢迎关注公号:BiggerBoy,看更多文章

原文链接:https://mp.weixin.qq.com/s?__biz=MzUxNTQyOTIxNA==&mid=2247485158&idx=1&sn=e5ef38a44436b9bf263f68d57fe05b5f&chksm=f9b780d7cec009c194e68f1ddec29939809d519f722eae73406f06d18bb0986eb96644f4e9bf&token=878840602&lang=zh_CN#rd

往期精品

用法

​

此时commentAnalyses为Page对象(PageHelper插件包内定义的)

​

而Page对象继承自JDK中的ArrayList,扩展并封装了一些page相关的字段,如页码,每页大小,总记录数,总页数等。

​

原理

我们就加了一行,它是如何帮助我们实现分页的呢?请往下看。

PageHelper.startPage做了什么

我们看这一行PageHelper.startPage(pageIndex, pageSize);做了什么。这个类中重载了好多个startPage方法,最终调用到如下的一个方法

​

可以看到该方法将分页信息作为构造器参数实例化Page对象,调用SqlUtil.getLocalPage()获取一个旧的Page对象,最后调用SqlUtil.setLocalPage(page);把新创建的Page对象set进去。

我们看看这两个方法做了什么。如下,很简单,从ThreadLocal中获取Page对象,将Page对象set到ThreadLocal中。知道ThreadLocal作用的不用多说,不知道的可以理解为用于保存本地变量,并与线程绑定。

​

看到这我们暂且将这一行的作用记为,创建并保存Page对象(分页信息)到ThreadLocal中。

Page分页信息在哪使用

那么既然保存了,就有使用的地方。

我通过代码追踪的方式定位到被调用的地方,通过回溯,发现是从这个类com.github.pagehelper.dialect.AbstractDialect发起调用的

​

点进去看了一下,主要是取出Page对象用于做一些判断,或保存page相关的信息。应该后续会涉及到

​

拦截器

上述AbstractDialect类中的这些方法再回溯,指向了

com.github.pagehelper.util.SqlUtil#doIntercept方法,intercep调用了doIntercep方法,

​

继续往上追踪来到了com.github.pagehelper.PageHelper#intercept,这是Interceptor接口的方法。

​

然后是org.apache.ibatis.plugin.Plugin#invoke调用了com.github.pagehelper.PageHelper#intercept。

可以看到PageHelper实现了Interceptor,这个接口是Mybatis官方提供的,中文意思是拦截器,所以有可能是通过实现这个拦截器做了某些操作来实现分页的。

插件

通过代码追踪我们看到Interceptor的intercept方法是在Mybatis的一个org.apache.ibatis.plugin.Plugin类的invoke方法中调用的,而这个Plugin类实现了JDK的java.lang.reflect.InvocationHandler接口,这是JDK代理接口。

​

这个Plugin中有一个wrap方法会返回一个代理类,所以当调用这个代理类的方法时就会走到上面的invoke方法,就可能会进到拦截器的intercept方法。

​

所以我们看这个warp在哪调的,就知道啥时候创建这个代理类。就是在上面的PageHelper中,再贴一下代码

​

拦截器链

而这个PageHelper中的plugin方法是实现自Interceptor拦截器接口,所以会有一个地方统一调这个方法,往上追溯就会发现是在org.apache.ibatis.plugin.InterceptorChain拦截器链中调用的,如下。

​

该类有一个List保存所有拦截器,还有三个方法,分别是pluginAll用于调用所有拦截器的plugin方法,addInterceptor添加拦截器,getInterceptors获取拦截器链。

看到这大致明白了它的原理,PageHelper通过实现Mybatis的Interceptor接口实现分页,Mybatis通过InterceptorChain调用所有Interceptor。

加载&调用拦截器

那么我们看看Mybatis的拦截器是什么时候添加到拦截器链,什么时候被调用的。

通过代码追溯,发现在Configuration的addInterceptor方法中调用添加方法,Configuration.addInterceptor是在XMLConfigBuilder的pluginElement方法中被调用

​

而XMLConfigBuilder是解析XML方式的Mybatis的配置的,顾名思义pluginElement方法是解析XML中plugin相关的配置节点的

​

而我们确实在XML中配置了plugin

​

所以我们现在知道了mybatis的拦截器是在Mybatis解析配置文件时,解析plugins节点时添加到InterceptorChain中的。

拦截器什么时候调用的。我们看InterceptorChain的pluginAll方法在哪调的,通过代码追踪有如下四个地方调用拦截器链

​

@Intercepts注解

而PageHelper这个拦截器,我们可以发现这个类上有一个@Intercepts注解,这个注解接收的值为@Signature注解,在Signature注解配置了,Executor.class,query还有四个class:MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class。

​

了解Mybatis的插件机制的就明白了,这一行配置的意思是拦截Executor中的query方法,方法参数列表类型是MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class,就是下面这个方法。

​

所以看到这,我们可以断定InterceptorChain的pluginAll方法在上述调用点的第四个,也就是org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType),可想而知newExecutor,也就是创建Executor实例,可以断定,创建Executor时通过PageHelper的plugin方法包装了Executor,返回的是Executor的代理类。下面会讲到创建动态代理。

​

通过PageHelper创建代理对象

我们在正向回顾一下,如何调到PageHelper的。首先进入到pluginAll

​

然后会调到PageHelper的plugin方法,内部又调Plugin的warp方法

​

我们看看Plugin.wrap方法干了啥,代码如下。代码跟过来我们知道现在的target是Executor,interceptor是PageHelper。首先获取PageHelper拦截信息,然后筛选target是否是需要拦截的类型,这里会进入if判断逻辑,返回Executor的代理对象。

​

所以这时创建的Executor实例是代理对象,那么就会在某个时候调用代理的invoke方法(org.apache.ibatis.plugin.Plugin#invoke),invoke调Interceptor拦截器的intercept方法,从而调PageHelper的intercept方法执行分页逻辑

org.apache.ibatis.plugin.Plugin#invoke ==》 com.github.pagehelper.PageHelper#intercept

拦截器的调用源头-动态代理

因为返回的是Executor的动态代理,所以肯定是调用Executor的某个方法时触发进到invoke方法,具体在哪调的不好找。我们通过打断点的方式看是从哪进invoke方法的,首先断点打到Plugin的invoke方法内

​

通过调用栈看到是org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)调过来的,在invoke方法内判断了目标方法是不是我们要拦截的方法,因为PageHelper上注解的也是拦截这个方法,所以会进入到Plugin的invoke方法的第61行。所以就会进入到PageHelper的intercept方法,执行具体的拦截逻辑。

分页逻辑

思路就是拼SQL。

通过代码跟踪,最终的分页逻辑是在com.github.pagehelper.util.SqlUtil#doIntercept方法中,第162行,获取分页SQL,

调用

com.github.pagehelper.dialect.AbstractDialect#getPageSql(org.apache.ibatis.mapping.MappedStatement, org.apache.ibatis.mapping.BoundSql, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.cache.CacheKey)

​

在com.github.pagehelper.dialect.AbstractDialect#getPageSql(org.apache.ibatis.mapping.MappedStatement, org.apache.ibatis.mapping.BoundSql, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.cache.CacheKey)中调用com.github.pagehelper.dialect.AbstractDialect#getPageSql(java.lang.String, com.github.pagehelper.Page, org.apache.ibatis.session.RowBounds, org.apache.ibatis.cache.CacheKey),是一个抽象方法,具体实现有好多种

​

我们看mysql的,在原始SQL 拼接了" limit ?,?"

​

总结

以上是PageHelper实现分页的原理,总结一下:

Mybatis在四个地方留了扩展点,可以通过自定义拦截器实现Interceptor接口的plugin方法,执行自定义逻辑,可以通过该方法对Executor、ParameterHandler、ResultSetHandler、StatementHandler四个对象进行增强、扩展。

PageHelper实现了Interceptor接口,它的plugin方法调用Plugin.wrap方法对目标对象进行包装,包装成一个代理对象并返回,代理类的实现就是Plugin自身。

Plugin.wrap方法判断目标对象是否需要返回代理对象,判断依据是:Interceptor实现类(这里是PageHelper)上注解标注的类是否包含目标对象所属类。这里PageHelper 上注解标注参数为Executor对象,所以创建Executor对象会返回代理对象。

当调用Executor对象的方法时会进入到 Plugin.invoke 方法。invoke方法会判断是否需要走拦截器的intercept方法,判断方式是取拦截器上的注解标注的方法,这里 PageHelper 标注的为executor.query(四个参数的那个),所以调这个时才会被拦截器拦截,其余方法还用原始对象调用。

PageHelper 的intercept方法调用SqlUtil的intercept方法最终调SqlUtil.doIntercept方法。在这个方法里会执行count语句,并将结果放到page对象里,然后判断需要分页,则将分页sql拼在原始sql上,然后执行。

简单来说就是通过mybatis的拦截器和插件实现的,PageHelper实现了Interceptor拦截器接口,并拦截Executor的query方法,在执行前PageHelper会在原始SQL前拼装分页相关的SQL。

PageHelper支持以下数据库的分页:Db2、Hsqldbt 、Informix、MySq、Oracle 、SqlServer2012、SqlServer

mybatis的插件Plugin是通过JDK动态代理对目标对象进行增强

不知道我说清楚了没有。欢迎探讨。

​

Mybatis第三方PageHelper分页插件原理的更多相关文章

  1. SpringBoot+Mybatis配置Pagehelper分页插件实现自动分页

    SpringBoot+Mybatis配置Pagehelper分页插件实现自动分页 **SpringBoot+Mybatis使用Pagehelper分页插件自动分页,非常好用,不用在自己去计算和组装了. ...

  2. Mybatis的PageHelper分页插件的PageInfo的属性参数,成员变量的解释,以及页面模板

    作者:个人微信公众号:程序猿的月光宝盒 //当前页 private int pageNum; //每页的数量 private int pageSize; //当前页的数量 private int si ...

  3. Springboot 系列(十二)使用 Mybatis 集成 pagehelper 分页插件和 mapper 插件

    前言 在 Springboot 系列文章第十一篇里(使用 Mybatis(自动生成插件) 访问数据库),实验了 Springboot 结合 Mybatis 以及 Mybatis-generator 生 ...

  4. 【spring boot】14.spring boot集成mybatis,注解方式OR映射文件方式AND pagehelper分页插件【Mybatis】pagehelper分页插件分页查询无效解决方法

    spring boot集成mybatis,集成使用mybatis拖沓了好久,今天终于可以补起来了. 本篇源码中,同时使用了Spring data JPA 和 Mybatis两种方式. 在使用的过程中一 ...

  5. Spring Boot整合tk.mybatis及pageHelper分页插件及mybatis逆向工程

    Spring Boot整合druid数据源 1)引入依赖 <dependency> <groupId>com.alibaba</groupId> <artif ...

  6. springboot如何集成mybatis的pagehelper分页插件

    mybatis提供了一个非常好用的分页插件,之前集成的时候需要配置mybatis-config.xml的方式,今天我们来看下它是如何集成springboot来更好的服务的. 只能说springboot ...

  7. 小白的springboot之路(十五)、mybatis的PageHelper分页插件使用

    0.前言 用mybatis,那么分页必不可少,基本都是用PageHelper这个分页插件,好用方便: 1.实现 1.1.添加依赖: <!-- 3.集成 mybatis pagehelper--& ...

  8. 后端——框架——持久层框架——Mybatis——补充——pageHelper(分页)插件

    Pagehelper插件的知识点大致可以分为三个部分 搭建环境,引入jar包,配置. 使用方式,只需要记住一种即可.类似于在写SQL语句中,可以left join,也可以right join,它们实现 ...

  9. Mybatis学习 PageHelper分页插件

    1.Maven依赖,注意使用PageHelper时的版本必须与Mybatis版本对应 1 <!-- 添加Mybatis依赖 --> 2 <dependency> 3 <g ...

随机推荐

  1. vue 因为使用scope后选择器和标签出现[data-v

    使用scope的以后出现datd-v,例如: <div data-v-2311c06a class="button-warp"> <button data-v-2 ...

  2. Kubernetes最佳实践之腾讯云TKE 集群组建

    作者陈鹏,腾讯工程师,负责腾讯云 TKE 的售中.售后的技术支持,根据客户需求输出合理技术方案与最佳实践,为客户业务保驾护航.使用 TKE 来组建 Kubernetes 集群时,会面对各种配置选项,本 ...

  3. MySQL常用内置函数整理

    [1]@@datadir 函数作用:返回数据库的存储目录构造SQL语句 select @@datadir;ps:@@basedir返回mysql的根目录[2]@@version_compile_os ...

  4. ASCII、Unicode和UTF-8等常见字符编码格式介绍

    信息存储在计算机中是转换成二进制来存储的,二进制的发明据说是来源于中国阴阳八卦.后德国数理哲学大师莱布尼茨是最早接触中华文化的欧洲人之一,从他的传教士朋友鲍威特寄给他的拉丁文译本<易经>中 ...

  5. 《剑指offer》面试题59 - I. 滑动窗口的最大值

    问题描述 给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值. 示例: 输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3 输出: [3,3,5,5 ...

  6. 加深对AQS原理的理解示例二:自己设计一个同步工具,同一时刻最多只有两个线程能访问,超过线程将被阻塞

    /** *@Desc 设计一个同步工具,同一时刻最多只有两个线程能访问,超过线程将被阻塞<br> * 思路分析: * 1.共享锁 两个线程及以内能成功获取到锁 * 2. *@Author ...

  7. 返回值ModelAndView

  8. [源码分析] Facebook如何训练超大模型---(4)

    [源码分析] Facebook如何训练超大模型 --- (4) 目录 [源码分析] Facebook如何训练超大模型 --- (4) 0x00 摘要 0x01 背景知识 1.1 单精度.双精度和半精度 ...

  9. 带你十天轻松搞定 Go 微服务系列(三)

    序言 我们通过一个系列文章跟大家详细展示一个 go-zero 微服务示例,整个系列分十篇文章,目录结构如下: 环境搭建 服务拆分 用户服务(本文) 产品服务 订单服务 支付服务 RPC 服务 Auth ...

  10. 沁恒CH32F103C8T6的开发和烧录配置说明

    概述 CH32F1系列是沁恒生产的32位Cortex-M3 MCU, 片上集成了时钟安全机制.多级电源管理. 通用DMA控制器等. 此系列具有 2 路 USB2.0接口.多通道 TouchKey. 1 ...