MyBatis的运行分为两大部分,第一部分是读取配置文件缓存到Configuration对象,用以创建SqlSessionFactory,第二部分是SqlSession的执行过程。相对而言,SqlSessionFactory的创建比较容易理解,而SqlSession 的执行过程远远不是那么简单了,本节我们解先来学习一下MyBatis是如何读取配置文件生成Configuration,并最终生成SqlSession的。
SqISessionFactory是MyBatis的核心类之一, 其最重要的功能就是提供创建MyBatis的核心接口SqlSession,所以我们需要先创建SqlSessionFactory,为此我们需要提供配置文件和相关的参数。而MyBatis是一一个 复杂的系统,采用建造者模式去创建SqlSessionFactory,我们可以通过SqlSessionFactoryBuilder去构建。构建分为两步。
- 第一步,通过org.apache.ibatis.builder.xml.XMLConfigBuilder解析配置的XML文件,读出配置参数,并将读取的数据存入这个orgapache.ibatis.session.Configuration类中。注意,MyBatis几乎所有的配置都是存在Configuration里的。
- 第二步,使用Confinguration对象去创建SqlSessionFactory。MyBatis 中的SqlSessionFactory是一个接口,而不是实现类,为此MyBatis提供了一个默认的SqlSessionFactory实现类,我们一般 都会使用它org.apache.iatis.session.defaults.DefaultSqlSessionFactory。注意,在大部分情况下我们都没有必要自己去创建新的SqlSessionFactory的实现类。
特别需要注意的是,在构建SqlSessionFctory的过程中,MyBatis使用Builder设计模式,采用这种方式可以构建复杂的对象而且条理还很清晰,这一点是我们需要在源码中首先学习到的一点知识。接下来,详细的说明一下构建过程的详细过程。
1、构建Configuration
在SqlSessionFactory的构建过程中,Configuration的生成是接力比赛的“第一棒”,他能否成功生成直接决定了接下来流程是否可以继续执行。
我们来看一段代码:
public class SysConfigService {
public static void main(String[] args) throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try(SqlSession sqlSession = sqlSessionFactory.openSession()){
SysConfigMapper mapper = sqlSession.getMapper(SysConfigMapper.class);
mapper.selectAll().forEach(System.out::println);
}catch (Exception e){
e.printStackTrace();
}
}
}
程序启动执行,首先会由MyBatis的io包下的Resources类到指定的位置加载全局配置文件,方法最终调到了JDK的ClassLoader这个类的同名方法,方法内部核心的实现就一句话URL url = getResource(name);
。最终返回的类型是BufferedInputStream,这个也是Java的io包下的InputStream实现。
成功读取配置文件后就可以拿着输入流对象到SqlSessionFactoryBuilder中构建SqlSessionFactory了,继续跟踪源码,我们看看MyBatis是如何做的~~
跟随着DEBUG,我们来到了SqlSessionFactoryBuilder内部,来到源码的第68行,可以看到MyBatis使用了一个叫XMLConfigBuilder
的类来解析我们输入流中的东西。
继续追进去,我们看到它在构造方法中使用XMLPathParser来最终处理了我们的输入流,这个XMLPathParser是MyBatis的parsing包下的一个用于解析XML文件的一个类,它内部还是使用了Jdk提供的一个类XPath来解析XML文档,经过他解析XML成为一个DOM树(Document对象,也是JDK提供的)。拿到解析出来的对象,XMLConfigParser就会进入下图所示的另一个构造方法中执行Configuration对象的生成。
此时我们已经得到了XMLConfigBuilder对象,再看SqlSessionFactoryBuilder的build方法,将XMLConfigBuilder实例对象parser调用parser()方法得到的Configuration实例对象config作为参数,调用SqlSessionFactory接口的实现类DefaultSqlSessionFactory构造出SqlSessionFactory对象。
XMLConfigBuilder对象在调用parser()方法时,会读出所有所有配置文件,将配置文件解析后保存在Configuration对象中。(代码看起来很长,其实原理很简单,就是对那颗DOM树进行遍历)
XMLConfigBuilder的parseConfiguration(XNode)方法把XML全局配置文件中每一个节点的信息都读取出来,保存在一个Configuration对象中,Configuration分别对以下内容做出了初始化:
- properties 全局参数。
- settings 设置。
- typeAliases 别名。
- typeHandler 类型处理器。
- ObjectFactory对象。
- plugin插件。
- environment 环境。
- DatabaseIdProvider数据库标识。
- Mapper 映射器。
2、构建Mapper映射器
Mapper映射器是Configuration初始化组件的最后一道工序了,它就是加载我们sql映射文件或者是Mapper接口的地方,来,我们看看MyBatis为我们做了什么,如下图所示。

初始化Mapper的逻辑也不难理解:由parseConfiguration传入mappers结点,之后在mapperElement方法中遍历mappers结点下的所有子节点:
- 如果遍历到package子节点,是以包名引入映射器,则将该包下所有Class注册到Configuration的MapperRegistry中。
- 如果遍历到mapper子节点的class属性,则将指定的接口注册到Configuration的MapperRegistry中。
- 如果遍历到resource或者url子节点,麻烦一点,需要借助XMLMapperBuilder来解析SQL映射文件:
Mapper映射问文件最终还是通过JDk的XPathParser解析成一颗DOM树,在构造方法中完成初始化之后将调用XMLMapperBuilder的parse方法遍历这颗DOM树获取并解析其中的SQL语句。
在解析映射文件的时候首先它判断在当前configuration对象中是否已经有此资源了,这个在Configuration是叫一个Set类型的loadedResource变量控制的,这样设计是为了防止资源重复加载。当然如果没有加载,那就要执行加载操作了,进入到configurationElement方法中,如下图所示。
源码的逻辑也是不难的,首先获取namespace结点的值,这个namespace一般就是和这个Mapper相关联的那个Mapper接口,如果它没有的话,那就会在这里直接报错。否则namespace属性保存在XMLMapperBuilder的MapperBuilderAssistant对象中,以便其他方法调用。
之后方法对mapper映射文件每个标签逐一解析并保存到Configuration和MapperBuilderAssistant对象中,最后调用buildStatementFromContext方法解析select、insert、update和delete节点。
最终的一切都还是落到了XMLStatementBuilder类的parseStatementNode方法上,这个方法完成对每条SQL语句的解析并且把每一套SQL语句封装到一个SqlSource对象,SqlSource是个接口,如果是动态SQL就创建DynamicSqlSource实现类,否则普通SQL创建StaticSqlSource实现类。如下图。

这个方法就是把一条SQL语句色剂的所有可能的属性全部解析出来,并且如上面所说,它把每条SQL语句进行了封装用SqlSource来表示一条Sql语句,最后把这条Sql语句通过MapperBuilderAssistant的addMappedStatement方法将MappedStatement对象放入Configuration对象的mappedStatements容器中,得到了最终的Configuration对象后传入SqlSessionFactoryBuilder的另一个build方法中,生成我们需要的DefaultSqlSessionFactory对象。
这里只分析了MyBatis是如何解析resource和url方式指定的mapper配置文件,如果是通过指定Mapper接口的package或者class全限定名配置方式,则Configuration对象会通过addMappers方法将接口注册,再通过Java反射技术和JDK动态代理技术,根据接口class的全限定名找到对应的XML配置文件或者注解进行解析,如果是非注解模式的xml配置文件必须和这个class在同一级目录,且与class同名,这里不再详解 。
最后一图总结SqlSessionFactory的构建流程:
流程还是比较简单的,就是围绕着SqlSessionFactoryBuilder类该加载配置加载配置,该解析解析,最后得到Configuration对象返回给SqlSessionFactoryBuilder,之后SqlSessionFactoryBuilder拿着Configuration对象new了一个SqlSessionFactory的默认实现类DefaultSqlSessionFactory最后返回该对象。