源码级别解读 mybatis 插件

简介:spring

MyBatis logo

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎全部的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。sql

摘自官网。。数据库

为何要使用mybatis,相信看这篇文章的童鞋都有所了解,俺这里就多废话几句。在orm框架中,有轻量级的dbutils与mybatis,重量级的有hibernate。缓存

为何要选择mybatis呢?缘由以下:session

1.简单,这个无需我多废话。确定的啊,使用过的童鞋都知道,在dao层定义一个接口,而后定义一个对应的xml(xml中namespace的值对应接口的全限定名就ok。由于mybatis是经过namespaceid去和接口类进行映射,而后使用动态代理建立接口类的实例方法,参见动态代理).  mybatis

2.结果集映射,我认为这是mybatis作的最牛的一点,也是选择他最大的一个理由架构

3.完善的缓存机制(mybatis有1级缓存和2级缓存),1级缓存是基于会话的(Session),2级缓存是基于应用级别。这里不作过多阐述,有兴趣的能够参考文章 http://www.iteye.com/topic/1112327/框架

4.强大的插件机制。它能够在运行过程当中动态的执行你本身的业务逻辑,好比防止sql注入,分页,sql日志打印,sql执行耗时等均可以在插件中作。真正实现了业务与功能分离。让你随行所欲的在飞dao层中增长任何你想作的事情。ide

好了。上面说了那么多好处,本文中会有点穿插,但重点是插件机制。废话很少说,咱们开始缕一缕mybatis的流程ui

固然,最古老的方式就不去讲了(就是去调用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>

使咱们的拦截器生效。运行程序就会发现咱们的业务在对应的范围生效了。

文章编写的比较仓促,若是有问题能够留言给我,我会一一回复你们。谢谢

相关文章
相关标签/搜索