为什么需要 MyBatis 插件(Plugins)开发

点击上方“芋道源码”,选择“设为星标

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

 

来源:www.cnblogs.com/chenpi

/p/10498921.html


背景

关于Mybatis插件,大部分人都知道,也都使用过,但很多时候,我们仅仅是停留在表面上,知道Mybatis插件可以在DAO层进行拦截,如打印执行的SQL语句日志,做一些权限控制,分页等功能;但对其内部实现机制,涉及的软件设计模式,编程思想往往没有深入的理解。

本篇案例将帮助读者对Mybatis插件的使用场景,实现机制,以及其中涉及的编程思想进行一个小结,希望对以后的编程开发工作有所帮助。

注:本案例以mybatis 3.4.7-SNAPSHOT版本为例。

PS:文章是挺久之前写的,当时花了一些心思,存到电脑的word里,今天正好看到,就是里面的源码都是图片,哈哈哈,凑合着看吧。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能。

项目地址:https://github.com/YunaiV/ruoyi-vue-pro

Mybatis插件典型适用场景

分页功能

mybatis的分页默认是基于内存分页的(查出所有,再截取),数据量大的情况下效率较低,不过使用mybatis插件可以改变该行为,只需要拦截StatementHandler类的prepare方法,改变要执行的SQL语句为分页语句即可;

公共字段统一赋值

一般业务系统都会有创建者,创建时间,修改者,修改时间四个字段,对于这四个字段的赋值,实际上可以在DAO层统一拦截处理,可以用mybatis插件拦截Executor类的update方法,对相关参数进行统一赋值即可;

性能监控

对于SQL语句执行的性能监控,可以通过拦截Executor类的update, query等方法,用日志记录每个方法执行的时间;

其它

其实mybatis扩展性还是很强的,基于插件机制,基本上可以控制SQL执行的各个阶段,如执行阶段,参数处理阶段,语法构建阶段,结果集处理阶段,具体可以根据项目业务来实现对应业务逻辑。

基于微服务的思想,构建在 B2C 电商场景下的项目实战。核心技术栈,是 Spring Boot + Dubbo 。未来,会重构成 Spring Cloud Alibaba 。

项目地址:https://github.com/YunaiV/onemall

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执行流程,如下代码所示:

f07ea109e7f6cddb39b39bc9d5166237.png

以上代码主要完成以下功能:

  • 读取mybatis的xml配置文件信息

  • 通过SqlSessionFactoryBuilder创建SqlSessionFactory对象

  • 通过SqlSessionFactory获取SqlSession对象

  • 执行SqlSession对象的selectList方法,查询结果

  • 关闭SqlSession

如下是时序图,在整个时序图中,涉及到mybatis插件部分已标红,基本上就是体现在上文中提到的四个类上,对这些类上的方法进行拦截。

8a6b85149392bbf460ff14940e230848.png

Mybatis插件实现机制

插件配置信息的加载

先来看下mybatis是如何加载插件配置的,对应的xml配置信息如下:

cdcd87e0fb0872162d4d90304b893e75.png

对应的解析代码如下,主要做以下工作:

  1. 根据解析到的类信息创建Interceptor对象;

  2. 调用setProperties方法设置属性变量;

  3. 添加到Configuration的interceptorChain拦截器链中;

b333fe1bf95d86639207e1c3058f13e1.png

以上逻辑对应的时序图如下:

7e09b2dc0a4833ca459f10d36062418b.png

代理对象的生成

Mybatis插件的实现机制主要是基于动态代理实现的,其中最为关键的就是代理对象的生成,所以有必要来了解下这些代理对象是如何生成的。

Executor代理对象

0db397f5788b96cec1d389e6509e6d82.png

ParameterHandler代理对象

630dabab6be4ed37220372a985db2b40.png

ResultSetHandler代理对象

e941a78729d3c02e26e5259e02ce6fed.png

StatementHandler代理对象

75b76533445867f6866a5883f260edd3.png

观察源码,发现这些可拦截的类对应的对象生成都是通过InterceptorChain的pluginAll方法来创建的,进一步观察pluginAll方法,如下:

ab15f3dfb9f5b9e7e2bb93b1a8107286.png

遍历所有拦截器,调用拦截器的plugin方法生成代理对象,注意生成代理对象重新赋值给target,所以如果有多个拦截器的话,生成的代理对象会被另一个代理对象代理,从而形成一个代理链条,执行的时候,依次执行所有拦截器的拦截逻辑代码;

接下来看一下我们在编写拦截器的时候,一个典型的plugin方法实现方式,如下:

d50c02e54649a723db9c32f12c985d9f.png

再进一步查看wrap方法,如下:

典型的动态代理实现,调用的是Proxy.newProxyInstance方法来生成代理对象。

2eafb19ce061a26571b63c8b56d69f32.png

以上逻辑对应的时序图如下,这里我们假设声明了两个拦截器,那么在创建target代理对象的时候,最终返回的代理对象proxy2,实际上代理了proxy1,而proxy1又代理了target,:

ed413eeb8122b980e6b45c9243ec8719.png

拦截逻辑的执行

由于真正去执行Executor、ParameterHandler、ResultSetHandler和StatementHandler类中的方法的对象是代理对象(建议将代理对象转为class文件,反编译查看其结构,帮助理解),所以在执行方法时,首先调用的是Plugin类(实现了InvocationHandler接口)的invoke方法,如下:

首先根据执行方法所属类获取拦截器中声明需要拦截的方法集合;

判断当前方法需不需要执行拦截逻辑,需要的话,执行拦截逻辑方法(即Interceptor接口的intercept方法实现),不需要则直接执行原方法。

9b4a9ccff7eec86a5f8e87a555b5c71d.png

可以关注下Interceptor接口的intercept方法实现,一般需要用户自定义实现逻辑,其中有一个重要参数,即Invocation类,通过改参数我们可以获取执行对象,执行方法,以及执行方法上的参数,从而进行各种业务逻辑实现,一般在该方法的最后一句代码都是invocation.proceed()(内部执行method.invoke方法),否则将无法执行下一个拦截器的intercept方法。

以上逻辑对应的时序图如下,这里我们以执行executor对象的query方法为例,且假设有两个拦截器存在:

c720059ba141d8415489f5f30921e8c2.png

Mybatis插件开发例子

这里以分页插件为例,来了解下一般mybatis插件的编写规则,如下所示:

主要需要实现三个方法

  1. intercept:在此实现自己的拦截逻辑,可从Invocation参数中拿到执行方法的对象,方法,方法参数,从而实现各种业务逻辑, 如下代码所示,从invocation中获取的statementHandler对象即为被代理对象,基于该对象,我们获取到了执行的原始SQL语句,以及prepare方法上的分页参数,并更改SQL语句为新的分页语句,最后调用invocation.proceed()返回结果。

  2. plugin:生成代理对象;

  3. setProperties:设置一些属性变量;

9fb60a6f299f6b86e00ba18f04bea858.png

小结

简单的说,mybatis插件就是对ParameterHandler、ResultSetHandler、StatementHandler、Executor这四个接口上的方法进行拦截,利用JDK动态代理机制,为这些接口的实现类创建代理对象,在执行方法时,先去执行代理对象的方法,从而执行自己编写的拦截逻辑,所以真正要用好mybatis插件,主要还是要熟悉这四个接口的方法以及这些方法上的参数的含义;

另外,如果配置了多个拦截器的话,会出现层层代理的情况,即代理对象代理了另外一个代理对象,形成一个代理链条,执行的时候,也是层层执行;

关于mybatis插件涉及到的设计模式和软件思想如下:

  1. 设计模式:代理模式、责任链模式;

  2. 软件思想:AOP编程思想,降低模块间的耦合度,使业务模块更加独立;

一些注意事项:

  1. 不要定义过多的插件,代理嵌套过多,执行方法的时候,比较耗性能;

  2. 拦截器实现类的intercept方法里最后不要忘了执行invocation.proceed()方法,否则多个拦截器情况下,执行链条会断掉;



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢

45f59295b872da2985c8cb4301abfa20.png

已在知识星球更新源码解析如下:

b3d3a948cb2fff1bb014c28ce5513d45.png

416d62aeb79bfc2ae37c6359cb2a9e1d.png

b777027b65801efe818b334e1cc152fa.png

0a47ab4c441088b834d97ddbb133a2b6.png

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。
谢谢支持哟 (*^__^*)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值