Mybatis初始化过程简单总结

349 阅读5分钟

前面连续多篇文章都是在数据mybatis的初始化过程,目前基本完成,是时候做一个总结了。

总览

首先回顾下最上层的测试代码,实际上目前分析的还在测试代码中与mybatis相关的第一步,具体如下图:

目前还在构建SqlSessionFactory这行代码,这行代码涉及了很多流程,前面也分析了很多,这里把分析的主要流程总结如下图:

上图中只画出了SqlSessionFactory初始化过程以及mapper的加载过程,由于其他比如Configuration的属性、别名、插件、数据源配置、类型映射处理器的初始化过程比较简单并没有囊括进去。

上图中红色虚线框中的内容是SqlSessionFactory初始化过程,以外的是mapper的加载流程。

SqlSessionFactory加载过程

SqlSessionFactory的初始化使用的是建造者模式,在SqlSessionFactoryBuilder中有多种方法创建SqlSessionFactory,但是最终都是创建一个XMLConfigBuilder对象调用它的parse方法得到Configuration对象,最终调用build(Configuration config)方法创建DefaultSqlSessionFactory对象,而DefaultSqlSessionFactory只有一个属性config。

所以SqlSessionFactory的初始化实际上是mybatis的全局配置类Configuration的初始化。而它的初始通过XMLConfigBuilder的parse方法实现。

Configuration加载过程

XMLConfigBuilder的parse方法调用的是“parseConfiguration(parser.evalNode("/configuration"));”这行代码,parser是初始化XMLConfigBuilder时通过输入流解析出来的XPathParser对象,XPathParser是mybatis框架用来解析xml文件的类。

在拿到mybatis配置文件的configuration节点后,接下来就是对节点中的内容进行解析并填充到Configuration对象中。

在parseConfiguration方法中可以了解到别名、插件、数据源配置、类型映射处理器的配置过程。

从中可以知道自定义的插件都必须要实现Interceptor接口才行;

我们最常见的第一个也是必须的数据源节点是environments,从源码中了解到environments节点支持多个environment节点,每个environment节点都有id,只有id和environments节点的default相等的节点才会被加载进来

在这其中还自定义一个类型映射处理器,实现了Java集合在保存到数据库时自动转成字符串保存,并且在读取出来时转成Java集合。

Mapper加载过程

configuration加载最复杂的是mapper的加载过程了。在mybatis的配置xml文件中的mappers支持两种子节点:mapper、package。两个节点出现有严格的先后顺序,必须mapper节点在前面,package节点后面不能有任何mapper节点。

mapper节点又有三个属性resource、url、class,这三个属性有且只能存在一个,否则会抛出异常。其中resource、url是执行mapper.xml文件进行加载,class只是指定接口进行加载。

package节点是直接扫描指定包下面的所有接口,先根据接口加载对应的xml,然后如果有注解在解析注解。所以我们现在只需要指定mapper接口所在包就能够实现注解和xml都支持。

综上我把加载mapper根据代码流程分成了两类:根据接口加载、根据xml文件加载

根据接口加载流程图在总览图的右侧,首先是直接调用configuration的addMappers或者addMapper方法,这个方法调用的是MapperRegistry类的addMappers或者addMapper方法,最终会初始化一个MapperAnnotationBuilder对象执行他的parse方法。

parse方法会先调用loadXmlResource方法加载接口可能对应的mapper.xml文件,然后才是执行注解的解析。最终在解析到具体每个方法时调用parseStatement方法解析方法上面所有的注解信息,并通过MapperBuilderAssistant类的addMappedStatement方法创建MappedStatement对象并保存到configuration的mappedStatements中。

而根据xml加载mapper则是先根据resource、url创建XMLMapperBuilder对象并执行它的parse方法,parse方法调用configurationElement方法处理xml文件中mapper节点的内容,这个方法会解析出xml中的名称空间、缓存、参数映射、结果映射等信息,在最后解析具体的select|insert|update|delete节点。

解析语句节点时会创建XMLStatementBuilder对象,执行对象parseStatementNode方法去解析语句节点中所有信息,最后通过MapperBuilderAssistant类的addMappedStatement方法创建MappedStatement对象并保存到configuration的mappedStatements中。

可以看到无论是接口加载还是通过类加载最后都是生成一个MappedStatement对象并保存到configuration的mappedStatements中,mappedStatements是一个map,它是把MappedStatement对象id作为key,而id是如何生成的呢?

通过接口方式加载的MappedStatement对象id是接口的类名+“.”+方法名,在通过xml方式加载的是通过mapper节点的namespace属性的值(也就是名称空间)+“.”+每个节点的id属性值确定。

MappedStatement还有另外一个关键属性“SqlSource sqlSource;”。sqlSource是真正执行sql的具体表现,它提供了getBoundSql方法用来获取正真执行的sql,这个流程我们在后面详细分析。

总结

mybatis的初始化实际上是Configuration对象的初始化,是把mybatis的配置文件中信息加载到Configuration对象中的过程。

而其中最复杂的是mapper加载,无论哪种方式加载mapper最终都是一条sql执行语句对应一个MappedStatement对象,生成的MappedStatement对象保存在Configuration对象的属性“ Map<String, MappedStatement> mappedStatements”中, 在后面使用时通过Configuration对象获取对应sql执行。

Java程序员日常学习笔记,如理解有误欢迎各位交流讨论!