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 ...
随机推荐
- kubernetes 搭建教程
http://blog.csdn.net/u011563903/article/details/71037093
- AFNetworking 和 ASIHTTPRequest
在开发iOS应用过程中,如何高效的与服务端API进行数据交换,是一个常见问题.一般开发者都会选择一个第三方的网络组件作为服务,以提高开发效率和稳定性.这些组件把复杂的网络底层操作封装成友好的类和方法, ...
- Serv-u 外网访问内网的FTP服务器
1. 背景简介 最近研究如何在内网搭架FTP服务器,同时要保证外网(公网)能访问的到.终成正果,但走了一些弯路,在此记下,以飨后人. 2. 基础知识 FTP 使用 2 个端口,一个数据端口和一个命令端 ...
- $用python处理Excel文档(2)——用xlsxwriter模块写xls/xlsx文档
Refer:<python自动化运维:技术与最佳实践> 更多用法参考xlsxwriter官方文档:http://xlsxwriter.readthedocs.io/ 本文主要总结一下如何使 ...
- 【leetcode刷提笔记】Search Insert Position
Given a sorted array and a target value, return the index if the target is found. If not, return the ...
- 【鸟哥的Linux私房菜】笔记1
Linux是什么 从操作系统与cpu架构关系到linux Richard Mathew Stallman GPL 关于GNU计划 Linux的发展 Linux的核心版本 Linux的特色 Linux ...
- 20145230《Java程序设计》第5周学习总结
20145230 <Java程序设计>第5周学习总结 教材学习内容 本周主要学习的内容是关于异常处理的,感觉这部分内容对我们这种初学者 来说非常重要.举个例子,倘若你在编写一个Java程序 ...
- servlet原理分析
一.Servlet简介 Servlet是sun公司提供的一门用于开发动态web资源的技术. Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个Java程序向 ...
- 为什么可以Ping通IP地址,但Ping不通域名?
能否ping通IP地址,与能否解析域名是两回事不能ping通IP地址,说明对方禁止ICMP报文或对方没有开机等解析域名只是将域名翻译成IP地址,不论该IP地址是否能够正常访问 问题是ping域名的时候 ...
- 【P2052】道路修建(树形+搜索)
这个题看上去高大上,实际上就是一个大水题.怎么说呢,这个题思路可能比较难搞,代码实现难度几乎为0. 首先我们可以发现这是一棵树,然后问其中任意一条边左右两边的点的数量之差的绝对值,实际上,无论两边的点 ...