MyBatis系列之Mybatis源码解读

MyBatis的发展历程以及基本使用,详见

Mybaits系列之MyBatis的发展之路,怎么用好MyBatis

MyBatis工作流程分析 

全局配置文件 mybatis-Config.xml --> Mappr.xml -->Configuration填充配置类 -->SqlSessionFactory(生成session工厂) -->SqlSession(生成session)-->Executor(执行器)-->StatementHandler -->数据库

1、解析配置文件

2、创建工厂类

3、创建会话

4、操作数据库

 

MyBatis架构分层

1、提供给应用使用:接口层

2、处理数据库操作:核心层

3、支持工作:基础层

 

MyBatis源码分析

根据上面MyBatis工作流程,我们来想一想,Mybatis帮我们解析了什么文件,它是怎么解析的,产生了什么对象,这些对象又放在哪里?

   /**
     * 通过 SqlSession.getMapper(XXXMapper.class)  接口方式
     * @throws IOException
     */
    @Test
    public void testSelect() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession session = sqlSessionFactory.openSession(); // ExecutorType.BATCH
        try {
            BlogMapper mapper = session.getMapper(BlogMapper.class);
            Blog blog = mapper.selectBlogById(1);
            System.out.println(blog);
        } finally {
            session.close();
        }
    }

通过上面的构造方法,我们来看一看,到底经过了哪些具体的操作,mybatis才帮我们返回了一个Blog对象。

 

1、解析配置文件并且生成SqlSessionFactory

首先进入构造这个SqlSessionFactory工场的build方法,我们发现它是生成了一个XMLConfigBuilder对象,而这个对象里面new了一个全局配置对象,也就是我们Mybatis配置文件的Configuration。

 

 

 我们再进入这个parser的parse()方法来看看,从if判断我们可以看出这个Mybatis的xml配置文件只能加载一次,否则会丢出异常,然后通过这个XMLConfigBuilder的解析Node方法,把配置文件里面的所有属性以Xnode的形式返回,通过读取根标签configuration开始解析。

 看看parseConfiguration方法,我们发现它是把每个配置文件里的属性解析放在了Configuration的属性中,那下面这些其实也就是把我们的配置文件解析成属性,赋值到Configuration中。

 

 

 顺带提一下,我们发现它设置属性的同时,还有几个load操作,这几个操作是给我们的mybatis开启一些其它功能,比如上面的加载客户端的日志。

好,解析完这些属性的时候,中间突然插了一个settingElement方法,我们来看看这个方法做了什么。

这个方法里面就是设置mybatis给我们开启的功能了,如果你配置文件里面配置了,它就会读取替换,没有开启的就会用它默认的参数,比如延迟加载默认为false,比如缓存开启默认为true等,如果不知道某些值得默认属性得话,我们就可以来这里看看。

打开mybatis中文网也能看到这些配置,看看是不是与上面对应。

 

 再看settingElement方法之后得方法,主要看看mapperElement方法,显然这是解析mapper得方法。

 

我们常说mybatis查找mapper有几种方式,看到这段代码应该就能记住了,首先代码判断了是否是package标签,这也是我们常用得直接扫描整个包下面得mapper,然后如果不是,就解析下面三种标签,resource(直接指定mapper.xml的相对文件路径),url(直接指定mapper.xml的绝对文件路径),class(直接指定类路径,需要mapper类)。 看看mapper文件的解析方式。

mybatis会对应解析mapper文件里面的标签,里面有二级缓存,参数Map,返回的Map,解析的公共提取sql,重点看看它是怎么解析curd的标签的,怎么解决sql的硬编码问题。

 

mybatis解析完我们整个sql的标签以后,最终它会产生一个Mappedstatement对象,并把解析出来的,看看里面的属性是不是对应map,sqlsource放的是sql语句,statementType放的是curd标签等,果然,它把我们整个mapper里面的sql变成了一个一个的对象。

再回到mapperParse.parse方法,解析完mapper文件以后,还调用了一个bindMapperForNamespace()方法。

 

boundType获得那个mapper.xml对应的接口的全路径,然后调用了一个addMapper()方法,把这个mapper接口的全路径传进去。

 

最后,发现它是存了一组这样的键值对,这个mapper的class和这个mapper的代理工厂。

一直走到这里,就发现mybatis已经把我们的配置文件以及mapper.xml都解析完了,并且放在了配置类Configuration里面,最后返回一个DefaultsqlsessionFactory工厂了。

看看时序图:

 

2、SqlSessionFactory是怎么open一个SqlSession的 

通过上一步,我们发现解析完配置文件以后它会返回一个DefaultSqlSessionFactory对象,我们现在看看这个对象是怎么open一个SqlSession的,先跟踪方法。

通过这个方法,我们看到,它是从我们之前解析的配置文件里面,拿到了environment对象,同时创建了一个事务,然后又从配置中拿到属性,构造了一个执行器。

所以,mybatis里面的事务和执行器都是在openSession里面创建的,我们重点来看看执行器的构造。

 在这里我们可以看到,如果我们没有定义执行器的话,它会默认使用一个simple的执行器,同时为了防止有人手贱,设置一个执行器并且赋值null,所以加了一层null判断。

SimpleExecutor:封装了jdbc的statement,使用完后直接关闭。

ReuseExecutor:封装了jdbc的statement,使用完后不直接关闭,放在一个缓存的map里面等待下次使用。

BatchExecutor:封装了jdbc的statement,有缓存功能,同时支持批量操作。

还记得我们之前说到了mybatis的一级缓存和二级缓存,说过了一级缓存是放在baseExecutor中。

MyBatis系列之Mybatis缓存深入了解

可以看看这个执行器的类图,发现它们都是BaseExcutor的子实现类,里面有几个抽象的增删改查方法,如doUpdate(),doQuery(),留给子类去实现,典型的模板模式。

 

再回到这个方法,添加完执行器以后,它用包装类把这个Executor包装了一下,这里是为了二级缓存的实现,之后还调用了一个插件拦截器链的pluginAll方法,里面是把每个插件都关联一下这个执行器,最后返回这个执行器。

 

最后又回到一开始那个方法,返回了一个带有Configruation,executor的defaltSqlSession 。

 

3、Session来得到Mapper 

 上一步我们已经拿到了一个DefaltSqlSession,现在我们再来看看它是怎么得到一个可以调用curd的mapper的,看看它具体做了哪些操作。

 

 

 

看到这里是不是恍然大悟,我们第一次获得一个defaltSqlSessionFactory的时候,最后一步是不是把接口类和对应的代理工厂绑定在了一起,而这里就是通过这个接口类来获取这个对应的工厂,并且把sqlSession作为参数来new一个实例,最后,它又把这个实例传给了另一个newInstance方法,没错,非常明显的jdk动态代理,接口的类加载器,接口和被代理的对象。

 

4、代理对象mapperProxy执行具体的sql操作

对最后一行代码进行调试发现,怎么直接去了接口方法,😂😂,当然了!代理对象调用的方法不都在invoke里面吗,此时当然是去看mapperProxy的invoke方法了。

 

看这个方法,第一步,它首先会去看看它调用的是不是object本身的方法, 如果是就直接调用这个方法;第二步,判断你是不是接口中的defalt方法(jdk1.8中新加了很多接口中得defalut方法),如果是就走下面这个方法,有兴趣的自己可以去看看,否则,它会先拿到一个mapperMethod对象,然后用这个对象调用这个方法,所以重点又来了,这是个啥对象。

 来了来了,就在这里了,判断标签并且调用方法。

 

 mappedStatement对象,我们第一步封装sql使用的对象,终于在这里拿到了,通过方法的名字拿到了这个方法,也就是这段sql对应的sql对象,然后用执行器执行query方法,因为开启了缓存,我们进入缓存的执行器看看。

 

 原来,缓存保存一个sql,保存了这个sql的id,分页参数,sql语句以及传参值,这样缓存就能够确保找到这个对应的sql了,好,继续往下走看到这个query方法。

从这一步可以看出,它是先从我们的二级缓存中取这个值,通过我们之前保存的参数,如果取不到,就走下面的方法。 

 

如果没有二级缓存,它就会走一级缓存中的方法,也就是baseExecutor中的query方法,其中走数据库的方法就是上面那个queryFromDatabase了,终于要揭开他的真面目了。

 

我们看看默认的simpleExecutor执行器是怎么执行的。

 

 发现它创建preparedstatement的时候,先把这些参数赋值,比如配置文件,执行器,sql对象以及分页参数,同时最后还构造了两个handler,一个是参数处理器,一个是结果处理器。

 到目前为止,我们已经发现了mybatis里面的四个handler了,分别是executor,执行器的拦截,statement,jdbc的拦截,parameter,参数的拦截以及resultset,返回结果集的拦截。

看到了,下面一个query方法,就是prepareStatement的execute方法了,操作数据库,并且把返回的结果集传给resultSetHandler来处理。

然后就是返回结果集,转换成java对象的操作了。

好,以上的流程就是mybatis怎么通过配置文件和mapper文件执行sql的整体过程了,有兴趣的小伙伴可以继续去看看最后是怎么操作把结果集转换成java对象的,无非就是反射,然后从resultType里面取到类型,然后赋值等一些列操作了。