Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的)。

Mybatis支持对ExecutorStatementHandlerParameterHandlerResultSetHandler进行拦截。

插件的运行时的逻辑:

  1. 所有可能被拦截的处理类都会生成一个代理。
  2. 代理类在执行对应方法时,判断要不要执行插件中的拦截方法。
  3. 执行插件中的拦截方法后,推进目标的执行。

Executor

前面博客介绍过Executor是在openSession()的过程中被创建的。在调用ConfigurationnewExecutor()方法创建Executor时会进行以下操作。

1
executor = (Executor) interceptorChain.pluginAll(executor);

每一个拦截器对目标类都进行一次代理,层层进行。

executor执行了多次plugin,第一次plugin后通过Plugin.wrap方法生成了第一个代理类,姑且就叫executorProxy1,这个代理类的target属性是该executor对象。第二次plugin后通过Plugin.wrap方法生成了第二个代理类,姑且叫executorProxy2,这个代理类的target属性是executorProxy1…这样通过每个代理类的target属性就构成了一个代理链。

InterceptorChain
1
2
3
4
5
6
public Object (Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}

Interceptor

拦截器Interceptor接口定义了三个方法:

  • intercept(): 内部要通过invocation.proceed()显式地推进责任链前进,也就是调用下一个拦截器拦截目标方法。
  • plugin(): 用当前这个拦截器生成对目标target的代理,实际是通过Plugin.wrap(target,this) 来完成的,把目标target和拦截器this传给了包装函数。
  • setProperties(): 用于设置额外的参数,参数配置在拦截器的Properties节点里。
自定义拦截器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package plugin;
import org.apache.ibatis.executor.Executor;
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.Properties;
({@Signature(
type= Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class MyPlugin implements Interceptor{
@Override
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}

Plugin

生成拦截器代理对象是在Plugin.wrap()中完成的,Plugin本身实现了InvocationHandler(JDK代理实现)。

  • target: 被代理的目标类。
  • interceptor: 对应的拦截器。
  • signatureMap: 拦截器拦截的方法缓存。

Plugin代理对象的invoke()完成对目标类的方法调用。如果方法签名和拦截中的签名一致,就调用拦截器的拦截方法intercept(),传递的是一个Invocation对象。

Plugin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Plugin implements InvocationHandler {
...
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return大专栏  Mybatis: 插件及分页pan> Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
...
}

Invocation

Invocation对象保存了代理对象的目标类,执行的目标类方法以及传递给它的参数,真正执行目标类的方法调用是在Invocation中的proceed()方法。
所以代理对象的invoke()中调用拦截器的intercept(Invocation invocation)方法后,在该方法还必须调用invocation.proceed()方法才能使代理链继续执行下去

Invocation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package org.apache.ibatis.plugin;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Invocation {
private Object target;
private Method method;
private Object[] args;
...
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}

详情参见:Mybatis 插件原理

分页

Mybatis的分页功能是基于内存的分页,也就是查出所有记录再按照偏移量offset和limit取出结果。在大数据量的情况下不适用,下面两个插件都是通过拦截Executor等重写sql语句实现数据库的物理分页。

逻辑分页(内存分页)

Mybatis 自身通过 RowBounds来完成内存分页的。

Test
1
2
3
4
List<Actor> actorsInMem = mapper.selectAll(new RowBounds(10, 5));
for (Actor actor: actorsInMem){
System.out.println(actor);
}
ActorMapper
1
List<Actor> selectAll(RowBounds rowBounds);
ActorMapper.xml
1
2
3
4
5
<select id="selectAll" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from actor
</select>
输出结果(第3页数据)
1
2
3
4
5
Actor{actorId=11, firstName='ZERO', lastName='CAGE', lastUpdate=Wed Feb 15 04:34:33 CST 2006}
Actor{actorId=12, firstName='KARL', lastName='BERRY', lastUpdate=Wed Feb 15 04:34:33 CST 2006}
Actor{actorId=13, firstName='UMA', lastName='WOOD', lastUpdate=Wed Feb 15 04:34:33 CST 2006}
Actor{actorId=14, firstName='VIVIEN', lastName='BERGEN', lastUpdate=Wed Feb 15 04:34:33 CST 2006}
Actor{actorId=15, firstName='CUBA', lastName='OLIVIER', lastUpdate=Wed Feb 15 04:34:33 CST 2006}

物理分页

物理分页就是在SQL查询过程中实现分页,不同的数据库厂商,实现也会不同。MySql通过在sql语句中添加offset和limit实现。

分页插件实现,通过添加拦截器,对Executor进行拦截,然后重写sql:

详情参见:Mybatis 分页

Mybatis: 插件及分页的更多相关文章

  1. Mybatis插件原理和PageHelper结合实战分页插件(七)

    今天和大家分享下mybatis的一个分页插件PageHelper,在讲解PageHelper之前我们需要先了解下mybatis的插件原理.PageHelper 的官方网站:https://github ...

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

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

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

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

  4. Mybatis中使用PageHelper插件进行分页

    分页的场景比较常见,下面主要介绍一下使用PageHelper插件进行分页操作: 一.概述: PageHelper支持对mybatis进行分页操作,项目在github地址: https://github ...

  5. springboot结合mybatis使用pageHelper插件进行分页查询

    1.pom相关依赖引入 <dependencies> <dependency> <groupId>org.springframework.boot</grou ...

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

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

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

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

  8. mybatis插件机制及分页插件原理

    MyBatis 插件原理与自定义插件: MyBatis 通过提供插件机制,让我们可以根据自己的需要去增强MyBatis 的功能.需要注意的是,如果没有完全理解MyBatis 的运行原理和插件的工作方式 ...

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

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

随机推荐

  1. mysql 几个坑

    浮点转int类型 更新合并换成插入 不要频繁更新超过20个字节的字段 程序逻辑得到数据处理后才还回连接

  2. 12)PHP,常量和魔术常量

    义:用于存储一个不会变化也不希望变化的数据的标示符. 常量命名规则,同变量,但习惯说,常常将常量的名称使用“全大写”形式. 定义形式 使用define()函数定义 使用形式:define(“常量名”, ...

  3. 吴裕雄--天生自然 pythonTensorFlow自然语言处理:Seq2Seq模型--训练

    import tensorflow as tf # 1.参数设置. # 假设输入数据已经用9.2.1小节中的方法转换成了单词编号的格式. SRC_TRAIN_DATA = "F:\\Tens ...

  4. win10+CUDA9.0176、CUDNN7.6.0安装

    在github上下载了一个文本分类的代码,包含了CNN.LSTM等分类模型,运行时说我的CUDA版本不行,我原来是9.1,让我安装9.0 然后开始卸载9.1啊,在此感谢博主:https://blog. ...

  5. axios 模拟同步请求

    axios本身没有同步请求,但是我们很多情况下必须得需要同步请求.那么应该怎么做? 上网查了一些资料有人说用es6的 async +  assert 我不知道有没有效果,因为我的功能中是没啥效果的. ...

  6. 简单的使用httpclient读取网页html例子

    public void clientPost(String url) {  /* 1 生成 HttpClinet 对象并设置参数*/    HttpClient httpClient=new Http ...

  7. 实验报告8 AC+Fit AP组网通过三层网络注册(DHCP Option 43)

    实验报告8 课程名称 无线网络与安全技术 实验名称 AC+Fit AP组网通过三层网络注册(DHCP Option 43) 姓名 学号 班级 实 验 目 的   [实验目的] 了解AC+Fit AP跨 ...

  8. text-overflow属性

    text-overflow属性有两个值, 默认值是clip:当对象内文本溢出时不显示裁切掉. 另一个就是:ellipsis:对象内文本溢出时显示省略标记(...). 使用text-overflow:e ...

  9. CPU内核、用户模式

    本文由是阅读该文章做下的笔记. CPU分内核与用户模式. 三言蔽之 内核模式下,应用可以直接存取内存,能够执行任何CPU指令.一般来说驱动运行在该模式下.内核模式的应用一旦崩溃,整个操作系统都会崩溃. ...

  10. XRichText

    XRichText是一个可以显示Html富文本的TextView.可以用于显示新闻.商品详情等场景.欢迎star.fork,提出意见. 使用 Gradle : compile 'cn.droidlov ...