源码级别解读 mybatis 插件
Posted 芋道源码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码级别解读 mybatis 插件相关的知识,希望对你有一定的参考价值。
简介:
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
摘自官网。。
为什么要使用mybatis,相信看这篇文章的童鞋都有所了解,俺这里就多废话几句。在orm框架中,有轻量级的dbutils与mybatis,重量级的有hibernate。
为什么要选择mybatis呢?原因如下:
1.简单,这个无需我多废话。肯定的啊,使用过的童鞋都知道,在dao层定义一个接口,然后定义一个对应的xml(xml中namespace的值对应接口的全限定名就ok。因为mybatis是通过namespaceid去和接口类进行映射,然后使用动态代理创建接口类的实例方法,参见动态代理).
2.结果集映射,我认为这是mybatis做的最牛的一点,也是选择他最大的一个理由
3.完善的缓存机制(mybatis有1级缓存和2级缓存),1级缓存是基于会话的(Session),2级缓存是基于应用级别。这里不做过多阐述,有兴趣的可以参考文章 http://www.iteye.com/topic/1112327/
4.强大的插件机制。它可以在运行过程中动态的执行你自己的业务逻辑,比如防止sql注入,分页,sql日志打印,sql执行耗时等都可以在插件中做。真正实现了业务与功能分离。让你随行所欲的在飞dao层中增加任何你想做的事情。
好了。上面说了那么多好处,本文中会有点穿插,但重点是插件机制。废话不多说,我们开始缕一缕mybatis的流程
当然,最古老的方式就不去讲了(就是去调用mybatis select, selectOne这种方式),我们直接从接口的玩法中入手
这是mybatis的一张架构图,从中我们可以看得出。应用程序去调用对应的db操作时会经历 配置->打开会话->在会话中执行相应的操作,同时会话中包含了jdbc的事务(对,没错。就是jdbc的事务)。那么我们可以看得出来,会话是核心。而配置是关键。
我们可以打开配置类看看,中间有很多属性。我们绝大多数都可以忽略,但是
这个就是mybatis内部维护的拦截器链。我们在解析配置,初始化的时候会给xml中写入一个拦截器的类,他是从
Configuration.addInterceptor(Interceptor interceptor)
这个方法中写入。这一点对于本文来讲很重要,请大家牢记。
接下来说会话。有心急的小伙伴就会提问,插件和会话有关系吗?
必然的事情,要不然我也不会罗里吧嗦的说这么多关于会话的东西。我们可以看看sqlsession的结构
可以看得出来,他有2个实现类(用过spring的童鞋应该知道,sqlsessionTemplate是spring对sqlsession的简单封装,这里不再赘述), 那么就剩下了DefaultSqlSession和SqlSessionManager这两个类。
那么我们可以做一个大胆的假设,mybatis他默认打开的是DefaultSqlSession(其实在源码中是的)。我们要构建一个sqlsession的时候会使用 SqlSessionFactoryBuilder 这个类,通过传递一个抽象的Reader对象(是不是很眼熟?这玩意就是IO流里的字节流基类), 通过这个玩意传一个xml文件进来
然后去构建一个SqlSessionFactory对象。
可以看得出,它是new的defaultSqlSessionFactory这个类。我们跟进去看看
这里没什么稀奇的,无非就是把我们文件中的那些属性转移给这个Configuration类中,至于这个类里面具体有什么我就不带大家看了。我们继续看打开会话的流程
到这里他的会话构建就出来了,有心的童鞋这里应该能看到了吧,我上面说的jdbc事务和DefaultSqlSession
ok,我们打开这个类看看,里面全是一些模板方法。俺这里就不一一赘述
我们的重点是从
执行器(我暂且叫他执行器,也比较形象)开始
他这里就有好多个实现类了。老规矩。我就不带着大家找了,直接上图。默认的执行器类型是SimpleExecutor
找到这里,我们就会发现,这就是它的增删改查了。老规矩,不赘述
继续深入,接下来是插件部分的重点了:
在开始之前我们需要介绍一下,它的拦截器可以作用于
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
这些范围。
我在上面说过,interceptorChain 这个东东很重要,这就是我们的拦截器链。我们可以看到他会使用pluginAll(Object target)这个方法将所有拦截器加到拦截器链中。
而拦截器类是这样的。
如果我们需要实现一个自己的拦截器则需要实现这个接口中的方法。
其中
Object intercept(Invocation invocation) throws Throwable; // 这个方法是拦截器的业务方法
Object plugin(Object target); // 这个方法是对拦截器的包装, 如果不包装的话它是不会被加入到拦截器链中
void setProperties(Properties properties);// 这个方法是设置一些额外的属性
看明白这个之后我们自己手动去编写一个拦截器类
/** * @author Autorun * Created by Autorun on 2018/1/4. */@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}) }) public class LogInterceptor implements Interceptor { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public Object intercept(Invocation invocation) throws Throwable { return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } }
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}) })
这一行是标注要拦截哪个类上的方法,参数等。然后使用动态代理的invoke(Object obj, Object... args)方法,执行我们的逻辑
如果有多个拦截器则按照顺序(因为他内部是使用的ArrayList容器,有序可重复,大家都懂得)
具体执行的话是使用Plugin类中的Object invoke(Object proxy, Method method, Object[] args) throws Throwable 方法来做的。可以看得出来。它里面调用了我们编写的 Object intercept(Invocation invocation) throws Throwable;
之后我们需要把我们写的拦截器类注入到拦截器链中。
在mybatis的配置文件中加入
<!-- mybatis-config.xml --><plugins> <!-- 类的全限定名 --> <plugin interceptor="org.mybatis.example.ExamplePlugin"> </plugin></plugins>
使我们的拦截器生效。运行程序就会发现我们的业务在对应的范围生效了。
文章编写的比较仓促,如果有问题可以留言给我,我会一一回复大家。谢谢
以上是关于源码级别解读 mybatis 插件的主要内容,如果未能解决你的问题,请参考以下文章
#yyds干货盘点# mybatis源码解读:executor包(语句处理功能)