Mybatis_总结_06_用_插件开发
一、前言
Mybatis采用责任链模式,通过动态代理组织多个插件(拦截器),通过这些插件可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件。
二、会被拦截的接口
Mybatis 允许在映射语句执行过程中的某一点进行拦截调用。
默认情况下,Mybatis允许使用插件来拦截的接口和方法包括以下几个:
| 序号 | 接口 | 方法 | 描述 |
| 1 | Executor | update、query、flushStatements、commit、rollback、getTransaction、close、isClosed |
拦截执行器的方法 |
| 2 | ParameterHandler | getParameterObject、setParameters |
拦截参数的处理 |
| 3 | ResultSetHandler | handleResultSets、handleCursorResultSets、handleOutputParameters |
拦截结果集的处理 |
| 4 | StatementHandler | prepare、parameterize、batch、update、query |
拦截Sql语法构建的处理 |
Mybatis是通过动态代理的方式实现拦截的,阅读此篇文章需要先对Java的动态代理机制有所了解。可以参考博客《彻底理解java动态代理》
三、Mybatis四大接口
竟然Mybatis是对四大接口进行拦截的,那我们药先要知道Mybatis的四大接口对象 Executor, StatementHandler, ResultSetHandler, ParameterHandler。

图1-1 Mybatis框架执行过程
Mybatis插件能够对四大对象进行拦截,包括对Mybatis一次会话的所有操作进行拦截。可见Mybatis的插件的强大。
| 序号 | 接口 | 解读 |
| 1 | Executor |
是Mybatis的内部执行器。 它负责调用StatementHandler操作数据库,并把结果集通过 ResultSetHandler进行自动映射。 另外,他还处理了二级缓存的操作。从这里可以看出,我们也是可以通过插件来实现自定义的二级缓存的。 |
| 2 | StatementHandler |
是Mybatis直接和数据库执行sql脚本的对象。 另外它也实现了Mybatis的一级缓存。这里,我们可以使用插件来实现对一级缓存的操作(禁用等等)。 |
| 3 | ParameterHandler |
是Mybatis实现Sql入参设置的对象。 这里,使用插件可以改变我们Sql的参数默认设置。 |
| 4 | ResultSetHandler |
是Mybatis把ResultSet集合映射成POJO的接口对象。 我们可以定义插件对Mybatis的结果集自动映射进行修改。 |
四、插件Interceptor
Mybatis的插件实现要实现Interceptor接口,我们看下这个接口定义的方法。
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
这个接口只声明了三个方法。
1.setProperties
在Mybatis的配置文件中配置插件时,可通过此方法来传递参数给插件。
如,在mybatis-config.xml中,一般情况下,拦截器的配置如下:
<plugins>
<!-- 1.interceptor属性为拦截器实现类的全类名 -->
<plugin interceptor="tk.mybatis.simple.plugin.XXXInterceptor">
<!-- 2.通过property标签来配置参数,配置的参数在拦截器初始化时会通过setProperties方法传递给拦截器。 在拦截器中可以很方便的通过Properties取得配置的参数值 -->
<property name="prop1" value="value1" />
<property name="prop2" value="value2" />
</plugin>
</plugins>
2.plugin
此方法的参数target就是拦截器要拦截的对象,该方法会在创建被拦截的接口实现类时被调用 ???。
该方法的实现很简单,只需要调用Mybatis提供的Plugin(org.apache.ibatis.plugin.Plugin)类的wrap静态方法就可以通过Java的动态代理拦截目标对象。
这个方法的通常实现代码如下:
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
Plugin.wrap方法会自动判断拦截器的签名和被拦截对象的接口是否匹配,只有匹配的情况下才会使用动态代理拦截目标对象,因此在上面的实现方法中不必做额外的判断逻辑。
来看一个稍微复杂一点的例子。
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
if (target instanceof Executor) {
final Executor e = (Executor) target;
Executor executor = new Executor() {
public int update(MappedStatement ms, Object parameter) throws SQLException {
return e.update(ms, parameter);
} public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException {
return e.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
} public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
} public List<BatchResult> flushStatements() throws SQLException {
return e.flushStatements();
} public void commit(boolean required) throws SQLException {
e.commit(required);
} public void rollback(boolean required) throws SQLException {
e.rollback(required);
} public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
BoundSql boundSql) {
IRequest request = RequestHelper.getCurrentRequest(true);
boundSql.setAdditionalParameter("request", request);
return e.createCacheKey(ms, parameterObject, rowBounds, boundSql);
} public boolean isCached(MappedStatement ms, CacheKey key) {
return e.isCached(ms, key);
} public void clearLocalCache() {
e.clearLocalCache();
} public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key,
Class<?> targetType) {
e.deferLoad(ms, resultObject, property, key, targetType);
} public Transaction getTransaction() {
return e.getTransaction();
} public void close(boolean forceRollback) {
e.close(forceRollback);
} public boolean isClosed() {
return e.isClosed();
} public void setExecutorWrapper(Executor executor) {
e.setExecutorWrapper(executor);
}
}; return executor;
// return Plugin.wrap(executor, this);
}
return target;
}
上述代码中对匹配条件做了进一步的细化
3.intercept
此方法是Mybatis运行时要执行的拦截方法,
通过该方法的参数invocation可以得到很多有用的信息。
@Override
public Object intercept(Invocation invocation) throws Throwable{
Object target = invocation.getTarget();
Method method = invocation.getMethod();
Object[] args = invocation.getArgs();
Object result = invocation.proceed(); return result;
}
通过调用 invocation.proceed();可以执行被拦截对象真正的方法。proceed()方法实际上执行了method.invoke(target,args)方法、
4.多个插件的调用顺序
当配置多个拦截器时,Mybatis会遍历所有拦截器,按顺序执行拦截器的plugin方法,被拦截的对象就会被层层代理。
在执行拦截对象的方法时,会一层一层地调用拦截器,拦截器通过invocation.proceed()调用下一层的方法,直到真正的方法被执行。方法执行的结果会从最里面开始向外一层层返回,所以如果存在按顺序配置的ABC三个签名相同的拦截器,Mybatis会按照 C->B->A-> target.proceed() -> A->B->C

五、拦截器注解
除了需要实现拦截器接口外,还需要给实现类配置以下的拦截器注解:
(1)@Intercepts
(2)@Signature
使用这两个注解可以用来配置拦截器要拦截的接口。
1.注解说明
以拦截 ResultSetHandler 接口的 handleResultSets 方法为例,配置签名如下。
@intercepts({
@Signature(
type = ResultSetHandler.class,
method = "handleResultSets",
args = {Statement.class}
)
})
public class ResultSetInterceptor implements Interceptor
@Signature 注解主要包含以下三个属性:
(1)type :设置拦截的接口,可选值是前面提到的四个接口
(2)method :设置拦截接口中的方法名,可选值是前面4个接口对应的方法,需要和接口匹配。
(3)args :设置拦截器的参数类型
六、参考资料
Mybatis_总结_06_用_插件开发的更多相关文章
- Mybatis_总结_03_用_动态SQL
一.前言 MyBatis 的强大特性之一便是它的动态 SQL.如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦.例如拼接时要确保不能忘记添加必要的空格,还 ...
- GEF入门实例_总结_06_为编辑器添加内容
一.前言 本文承接上一节:GEF入门实例_总结_05_显示一个空白编辑器 在上一节我们为我们的插件添加了一个空白的编辑器,这一节我们将为此编辑器添加内容. 二.GEF的MVC模式 在此只简单总结一下, ...
- Eclipse插件开发_异常_01_java.lang.RuntimeException: No application id has been found.
一.异常现象 在运行RCP程序时,出现 java.lang.RuntimeException: No application id has been found. at org.eclipse.equ ...
- Eclipse插件开发_学习_00_资源帖
一.官方资料 1.eclipse api 2.GEF Developer's Guide 二. 精选资料 1.开发 Eclipse 插件 2.Eclipse, RCP, Plugin and OSGi ...
- [刘阳Java]_什么是MyBatis_第1讲
1.什么MyBatis,我们先通过百度百科先进行一个简单的了解 MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation ...
- 记录使用MyBatis_错误_警告_异常
1.使用MyBatis要非常仔细检查自己的sql语句有没有写错. jdbcType错误,有可能在控制台显示一个 builderException.
- 壹度DIY_微信小程序组件_小程序插件开发
开源免费插件,diy特有的页面机制,搭配30+自定义组件,让你的站点每一个页面都可以完全自定义,可无缝对接任意小程序,如有疑问加入qq壹度小程序交流群:302866773:或wx:liu2417301 ...
- 分布式_理论_06_ 一致性算法 Raft
一.前言 五.参考资料 1.分布式理论(六)—— Raft 算法 2.分布式理论(六) - 一致性协议Raft
- Eclipse插件开发_学习_02_GEF入门实例
一.前言 这一节,我们将会创建一个GEF入门实例 二.新建RCP项目 1. New 一个 Plug-in Project 2.输入项目名 项目名:com.ray.gef.helloworld 3.Co ...
随机推荐
- Redis3.2.8配置参数及说明
bind 127.0.0.1# 绑定的主机地址,不设置默认将处理所有请求protected-mode yes# 是否开启保护模式,默认开启,要是配置里面没有指定bind和密码,开启该参数后,redis ...
- checked 和 unchecked 基元类型操作
对基元类型执行的许多算术运算都可能造成溢出: Byte b = ; b = (Byte) (b + ); // b 现在包含 44(或者十六进制值 2C) 重要提示:执行上述算术运算时,第一步要求所有 ...
- 【整理学习Hadoop】H D F S 一个分布式文件系统
Hadoop分布式文件系统(HDFS)被设计成适合运行在通用硬件(commodity hardware)上的分布式文件系统.它和现有的分布式文件系统有很多共同点.但同时,它和其他的分布式文件系统的区别 ...
- $Android设置TextView的字体
做项目的时候,需要使用到手写字体来让内容更加的美观.可是程序中默认使用的是系统的默认字体,怎么将TextView(或EditText)的字体设置成自己想要的字体呢?步骤如下: 1.下载字体文件(.tt ...
- 树莓派连接DHT11温湿度传感器(python)
介绍 DHT11作为一个廉价配件,同时包含了温度.湿度传感器,而且,编码使用也非常简单. 本文介绍如果在树莓派中使用 DHT11,代码是Python.如果有任何疑问,欢迎在下面留言. 接线 VCC接5 ...
- 跨平台移动开发_PhoneGap API Camera 使用摄像头采集照片.
camera对象提供对设备默认摄像头应用程序的访问. 程序运行效果 相关代码 <!DOCTYPE html> <html> <head> <title> ...
- node拦截器设置
node的拦截器主要目的是用户登录的时候为用户存了一个session,用户登录后的其他操作都要经过拦截器,对比session的值,并把session的过期时间延长. 拦截器主要是在路由文件routes ...
- node做验证码
使用了ccap插件 1.安装: 通用方法:npm install ccap 2. cnst ccap= require('ccap')({ width: 128, height: 40, offset ...
- Java public class 与 class 区别
在编写类的时候可以使用两种定义方式: public class 定义类 class 定义类 1.public class 定义类 如果一个类声明的时候使用了public class,则类名必须与文件名 ...
- 第二节课-Data-driven approach:KNN和线性分类器分类图片
2017-08-12 1.图片分类是很多CV任务的基础: 2.图片分类要面临很多的问题,比如图片被遮挡,同一种动物有很多种颜色,形状等等,算法需要足够强壮: 3.所以很难直接写出程序来进行图片分类,常 ...