深入理解Mybatis插件开发
背景
关于Mybatis插件,大部分人都知道,也都使用过,但很多时候,我们仅仅是停留在表面上,知道Mybatis插件可以在DAO层进行拦截,如打印执行的SQL语句日志,做一些权限控制,分页等功能;但对其内部实现机制,涉及的软件设计模式,编程思想往往没有深入的理解。
本篇案例将帮助读者对Mybatis插件的使用场景,实现机制,以及其中涉及的编程思想进行一个小结,希望对以后的编程开发工作有所帮助。
注:本案例以mybatis 3.4.7-SNAPSHOT版本为例。
PS:文章是挺久之前写的,当时花了一些心思,存到电脑的word里,今天正好看到,就是里面的源码都是图片,哈哈哈,凑合着看吧。
Mybatis插件典型适用场景
分页功能
mybatis的分页默认是基于内存分页的(查出所有,再截取),数据量大的情况下效率较低,不过使用mybatis插件可以改变该行为,只需要拦截StatementHandler类的prepare方法,改变要执行的SQL语句为分页语句即可;
公共字段统一赋值
一般业务系统都会有创建者,创建时间,修改者,修改时间四个字段,对于这四个字段的赋值,实际上可以在DAO层统一拦截处理,可以用mybatis插件拦截Executor类的update方法,对相关参数进行统一赋值即可;
性能监控
对于SQL语句执行的性能监控,可以通过拦截Executor类的update, query等方法,用日志记录每个方法执行的时间;
其它
其实mybatis扩展性还是很强的,基于插件机制,基本上可以控制SQL执行的各个阶段,如执行阶段,参数处理阶段,语法构建阶段,结果集处理阶段,具体可以根据项目业务来实现对应业务逻辑。
Mybatis插件介绍
什么是Mybatis插件
与其称为Mybatis插件,不如叫Mybatis拦截器,更加符合其功能定位,实际上它就是一个拦截器,应用代理模式,在方法级别上进行拦截。
支持拦截的方法
- 执行器Executor(update、query、commit、rollback等方法);
- 参数处理器ParameterHandler(getParameterObject、setParameters方法);
- 结果集处理器ResultSetHandler(handleResultSets、handleOutputParameters等方法);
- SQL语法构建器StatementHandler(prepare、parameterize、batch、update、query等方法);
拦截阶段
那么这些类上的方法都是在什么阶段被拦截的呢?为理解这个问题,我们先看段简单的代码(摘自mybatis源码中的单元测试SqlSessionTest类),来了解下典型的mybatis执行流程,如下代码所示:
以上代码主要完成以下功能:
- 读取mybatis的xml配置文件信息
- 通过SqlSessionFactoryBuilder创建SqlSessionFactory对象
- 通过SqlSessionFactory获取SqlSession对象
- 执行SqlSession对象的selectList方法,查询结果
- 关闭SqlSession
如下是时序图,在整个时序图中,涉及到mybatis插件部分已标红,基本上就是体现在上文中提到的四个类上,对这些类上的方法进行拦截。
Mybatis插件实现机制
插件配置信息的加载
先来看下mybatis是如何加载插件配置的,对应的xml配置信息如下:
对应的解析代码如下,主要做以下工作:
- 根据解析到的类信息创建Interceptor对象;
- 调用setProperties方法设置属性变量;
- 添加到Configuration的interceptorChain拦截器链中;
以上逻辑对应的时序图如下:
代理对象的生成
Mybatis插件的实现机制主要是基于动态代理实现的,其中最为关键的就是代理对象的生成,所以有必要来了解下这些代理对象是如何生成的。
Executor代理对象
ParameterHandler代理对象
ResultSetHandler代理对象
StatementHandler代理对象
观察源码,发现这些可拦截的类对应的对象生成都是通过InterceptorChain的pluginAll方法来创建的,进一步观察pluginAll方法,如下:
遍历所有拦截器,调用拦截器的plugin方法生成代理对象,注意生成代理对象重新赋值给target,所以如果有多个拦截器的话,生成的代理对象会被另一个代理对象代理,从而形成一个代理链条,执行的时候,依次执行所有拦截器的拦截逻辑代码;
接下来看一下我们在编写拦截器的时候,一个典型的plugin方法实现方式,如下:
再进一步查看wrap方法,如下:
典型的动态代理实现,调用的是Proxy.newProxyInstance方法来生成代理对象。
以上逻辑对应的时序图如下,这里我们假设声明了两个拦截器,那么在创建target代理对象的时候,最终返回的代理对象proxy2,实际上代理了proxy1,而proxy1又代理了target,:
拦截逻辑的执行
由于真正去执行Executor、ParameterHandler、ResultSetHandler和StatementHandler类中的方法的对象是代理对象(建议将代理对象转为class文件,反编译查看其结构,帮助理解),所以在执行方法时,首先调用的是Plugin类(实现了InvocationHandler接口)的invoke方法,如下:
首先根据执行方法所属类获取拦截器中声明需要拦截的方法集合;
判断当前方法需不需要执行拦截逻辑,需要的话,执行拦截逻辑方法(即Interceptor接口的intercept方法实现),不需要则直接执行原方法。
可以关注下Interceptor接口的intercept方法实现,一般需要用户自定义实现逻辑,其中有一个重要参数,即Invocation类,通过改参数我们可以获取执行对象,执行方法,以及执行方法上的参数,从而进行各种业务逻辑实现,一般在该方法的最后一句代码都是invocation.proceed()(内部执行method.invoke方法),否则将无法执行下一个拦截器的intercept方法。
以上逻辑对应的时序图如下,这里我们以执行executor对象的query方法为例,且假设有两个拦截器存在:
Mybatis插件开发例子
这里以分页插件为例,来了解下一般mybatis插件的编写规则,如下所示:
主要需要实现三个方法
- intercept:在此实现自己的拦截逻辑,可从Invocation参数中拿到执行方法的对象,方法,方法参数,从而实现各种业务逻辑, 如下代码所示,从invocation中获取的statementHandler对象即为被代理对象,基于该对象,我们获取到了执行的原始SQL语句,以及prepare方法上的分页参数,并更改SQL语句为新的分页语句,最后调用invocation.proceed()返回结果。
- plugin:生成代理对象;
- setProperties:设置一些属性变量;
小结
简单的说,mybatis插件就是对ParameterHandler、ResultSetHandler、StatementHandler、Executor这四个接口上的方法进行拦截,利用JDK动态代理机制,为这些接口的实现类创建代理对象,在执行方法时,先去执行代理对象的方法,从而执行自己编写的拦截逻辑,所以真正要用好mybatis插件,主要还是要熟悉这四个接口的方法以及这些方法上的参数的含义;
另外,如果配置了多个拦截器的话,会出现层层代理的情况,即代理对象代理了另外一个代理对象,形成一个代理链条,执行的时候,也是层层执行;
关于mybatis插件涉及到的设计模式和软件思想如下:
- 设计模式:代理模式、责任链模式;
- 软件思想:AOP编程思想,降低模块间的耦合度,使业务模块更加独立;
一些注意事项:
- 不要定义过多的插件,代理嵌套过多,执行方法的时候,比较耗性能;
- 拦截器实现类的intercept方法里最后不要忘了执行invocation.proceed()方法,否则多个拦截器情况下,执行链条会断掉;
深入理解Mybatis插件开发的更多相关文章
- 《深入理解mybatis原理》 MyBatis事务管理机制
MyBatis作为Java语言的数据库框架,对数据库的事务管理是其很重要的一个方面.本文将讲述MyBatis的事务管理的实现机制. 首先介绍MyBatis的事务Transaction的接口设计以及其不 ...
- 《深入理解mybatis原理》 Mybatis初始化机制具体解释
对于不论什么框架而言.在使用前都要进行一系列的初始化,MyBatis也不例外. 本章将通过下面几点具体介绍MyBatis的初始化过程. 1.MyBatis的初始化做了什么 2. MyBatis基于XM ...
- 《深入理解mybatis原理》 MyBatis的架构设计以及实例分析
作者博客:http://blog.csdn.net/u010349169/article/category/2309433 MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简 ...
- 深入理解mybatis原理, Mybatis初始化SqlSessionFactory机制详解(转)
文章转自http://blog.csdn.net/l454822901/article/details/51829785 对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外.本章 ...
- Mybatis插件开发
前面几篇文章介绍了Mybtis中四个重要的对象,其中提到它们都是在Configuration中被创建的,我们一起看一下创建四大对象的方法,代码如下所示: public ParameterHandler ...
- 深入理解mybatis
MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单.优雅.本文主要讲述MyBatis的架构设计思路,并且讨论MyBatis的几个核心部件,然后结合一个select查询实例, ...
- 《深入理解mybatis原理7》 MyBatis的二级缓存的设计原理
<深入理解mybatis原理> MyBatis的二级缓存的设计原理 MyBatis的二级缓存是Application级别的缓存,它可以提高对数据库查询的效率,以提高应用的性能.本文将全面分 ...
- 《深入理解mybatis原理6》 MyBatis的一级缓存实现详解 及使用注意事项
<深入理解mybatis原理> MyBatis的一级缓存实现详解 及使用注意事项 0.写在前面 MyBatis是一个简单,小巧但功能非常强大的ORM开源框架,它的功能强大也体现在它的缓 ...
- 《深入理解mybatis原理4》 MyBatis缓存机制的设计与实现
<深入理解mybatis原理> MyBatis缓存机制的设计与实现 本文主要讲解MyBatis非常棒的缓存机制的设计原理,给读者们介绍一下MyBatis的缓存机制的轮廓,然后会分别针对缓存 ...
随机推荐
- Java对象转换成Map
需求总是千奇百怪,对象转成map,看似没必要,但就是有这个需求,所以记录下来 首先是Bean package tools; import lombok.Data; /** * 车辆实体类 */ @Da ...
- C# 串口操作系列(5)--通讯库雏形
C# 串口操作系列(5)--通讯库雏形 标签: 通讯c#数据分析byteclassstring 2010-08-09 00:07 21378人阅读 评论(73) 收藏 举报 分类: 通讯类库设计(4 ...
- “Hello, my first blog”------第一篇博客的仪式感
本人在校大学生一枚,开通博客,主要是想记录自己的学习过程,分享自己的学习经历.记得大一的时候,很多不懂的操作和知识,都是在博客上找到了相应的解决办法.但比较讽刺的是,很多时候,曾经解决了的问题,当再次 ...
- linux gdb
linux gdb linux 测试代码 #include <stdio.h> #include <stdlib.h> static char buf[255]; static ...
- vue定义全局组件
<!DOCTYPE html><html> <head> <meta charset="utf-8"> <title>& ...
- 算法第四版中 while (!StdIn.isEmpty()) 循环无法跳出问题
在IDEA中使用Ctrl+D就可以退出console输入
- 【安全性测试】drozer中关于AttackSurface的一些理解
在推荐扫描Android APP的工具中,扫描组件可以推荐drozer.使用过drozer的使用者知道,如何查找各个组件上的攻击层面 run app.package.AttackSurface . 它 ...
- vue-These relative modules were not found
今天在做vue2.0+webpack的项目的时候,本来一切正常,整理了一下文件夹分类,就是把一些基础的组件新建了一个文件夹移进去,然后就报了以下的错误,其他东西都没改 最后网上找了很多资料,有说配置文 ...
- Python(day1)
一.Python的属于解释型语言. 编译型:一次性,将全部的程序编译成二进制文件,然后再运行. 优点:运行速度快. 缺点:开发效率低,不能跨平台. 解释型:当你的程序运行时,一行一行的解释,并运行. ...
- 关于XML
一.XML定义 XML(eXtensible Markup Language)即可扩展标记语言,它与HTML一样,都是处于SGML,标准通用语言.Xml是Internet环境中跨平台的,依赖于内容的技 ...