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 ...
随机推荐
- jquery on 确认删除
$(document).on('click', '.delbtn', function() { if (confirm("确定要删除吗?")) { ...
- 如何高效地分析Android_log中的问题?——查看Android源码
在日常解bugs时,需要通过log日志来分析问题,例如查看crash发生时的堆栈信息时,就会有Android的源码的调用,这是就要去查看Android源码. 1.进入Android源码网址查看,例如 ...
- 剑指offer 面试58题
面试58题: 题目:翻转字符串 题:牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上.同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意 ...
- 通过ip得到所在城市,以及城市所在经纬度坐标(监控系统中用的该代码,小航哥)
监控系统中就是利用的该段代码,实现通过ip得到所在城市,以及城市所在经纬度坐标,最后得以利用echarts实现模拟迁移的效果 api官方介绍: http://lbsyun.baidu.com/inde ...
- SVN 过滤文件
SVN新手最容易犯的一个错误: 就是把所有文件一股脑地全提交上去了. 这样很不好,因为这当中包含很多编译器自动生成的文件,还有中间文件. 这些文件可能每次编译都会不同,所以编译一次就冲突一次. 很显然 ...
- SpringBoot整合Redis集群
一.环境搭建 Redis集群环境搭建:https://www.cnblogs.com/zwcry/p/9174233.html 二.Spring整合Redis集群 1.pom.xml <proj ...
- 主攻ASP.NET MVC4.0之重生:CheckBoxListHelper和RadioBoxListHelper的使用
在项目中新建Helpers文件夹,创建CheckBoxListHelper和RadioBoxListHelper类. CheckBoxListHelper代码 using System; using ...
- 主攻ASP.NET MVC4.0之重生:ASP.NET MVC Web API
UserController代码: using GignSoft.Models; using System; using System.Collections.Generic; using Syste ...
- CSS3 3D旋转按钮对话框
在线演示 本地下载
- string 类(二)
处理string对象中的字符: 在cctype头文件中定义了一组标准库函数来处理string对象中的字符,比如检查一个string对象是否包含空白,或者把string对象中的字母改成小写,再或者查看某 ...