Mybatis源码解读-初始化过程详解

在使用Mybatis时,我们通常将其配置在Spring容器中,当Spring启动的时候会自动加载Mybatis的所有配置文件然后生成注入到Spring中的Bean,本文从实用的角度进行Mybatis源码解读,会关注以下一些方面:

  1. Mybatis都有哪些配置文件和配置项
  2. Mybatis初始化的源码流程;
  3. Mybatis初始化后,产生了哪些对象;

Mybatis初始化环境并且执行SQL语句的JAVA代码

先看一段初始化Mybatis环境并且执行SQL语句的Java代码:

这段代码完成了这些事情:

1、读取Mybatis的配置文件

2、构建SqlSessionFactory

3、从SqlSessionFactory中创建一个SqlSession

4、使用SqlSession执行一个select语句,参数是Mapper.java的一个方法名

5、打印结果

在这里前三行代码包括读取配置文件和创建SqlSessionFactory,这就是Mybatis的一次初始化过程。

如果查看一下Spring配置Mybatis的文件,就会发现它使用mybatis-spring的包也主要是初始化了这个SqlSessionFactory对象:

该Spring配置sqlSessionFactory接收了三个参数,分别是数据源dataSource、Mybatis的主配置文件MapperConfig.xml、mapper.xml文件的扫描路径。

可以看出Mybatis的初始化过程就是读取配置文件然后构建出sqlSessionFactory的过程。

Mybatis都有哪些配置文件和配置项?

上面的Java代码中初始化Mybatis只使用了配置文件MapperConfig.xml,然而在Spring配置文件中构建sqlSessionFactory时也使用了mapper.xml配置文件,其实Mybatis最多也就这两类文件,主配置文件MapperConfig.xml可以通过XML元素包含普通的mapper.xml配置文件。

主配置文件:MapperConfig.xml

一个包含了所有属性的MapperConfig.xml实例:

主配置文件只有一个XML节点,就是configuration,它包含9种配置项:

  1. properties 属性:在这里配置的属性可以在整个配置文件中使用来替换需要动态配置的属性值
  2. settings 设置:MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为,比如是否使用缓存和日志记录的方式
  3. typeAliases 类型命名:类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。
  4. typeHandlers 类型处理器:无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
  5. objectFactory 对象工厂:MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。
  6. plugins 插件:MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用,比如增加分页功能、格式化输出最终的SQL等扩展;
  7. environments 环境:MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中,比如设置不同的开发、测试、线上配置,在每个配置中可以配置事务管理器和数据源对象;
  8. databaseIdProvider 数据库厂商标识:MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。
  9. mappers 映射器:MyBatis 的行为已经由上述元素配置完了,通过这里配置的mappers文件,我们可以去查找定义 的SQL 映射语句用于执行;

可以看出,前8个配置项用户设定Mybatis运行的一些环境,而第9个mappers映射器才是需要执行的SQL的配置,在正常情况下,我们只需要配置第9个mapper映射器的地址即可,前面的的Mybatis行为配置都有默认值正常情况下不需要设定。

包含最终SQL的mapper映射器配置文件

虽然我们经常写mapper文件,知道有select/insert/update/delete四种元素,还有sql/resultmap等配置项,就感觉配置项好多好多,但其实mapper总共也就8种配置,我们常用的6种就包含在内:

  1. cache – 给定命名空间的缓存配置。
  2. cache-ref – 其他命名空间缓存配置的引用。
  3. resultMap – 是最复杂也是最强大的元素,用于实现数据库表列和Java Bean的属性名的映射配置;
  4. sql – 可被其他语句引用的可重用语句块。
  5. insert – 映射插入语句
  6. update – 映射更新语句
  7. delete – 映射删除语句
  8. select – 映射查询语句

正常情况下,我们很少使用Mybatis提供的cache机制而是使用外部的Redis等缓存,所以这里的1和2的cache配置几乎不会使用,主要也就是我们平时使用的6种配置。

以上就是Mybatis所有提供给我们配置的地方,改变Mybatis行为的有8个配置项,每个XML配置文件刚好也最多有8个配置项,总共有16个配置项。

Mybatis初始化的源码流程

阅读Mybatis源码最好的方式,就是从源码中的单测作为入口,然后DEBUG一步步的执行,在自己关注的地方多多停留一会仔细查看。

以下以代码的流程进行解析,只贴出主要的代码块:

Mybatis代码初始化入口

这里看到,进入了new SqlSessionFactoryBuilder().build(reader)方法。

进入SqlSessionFactoryBuilder的build方法

主要两行在try块内,第一行的内容是调用XPathParser加载了Mybatis的主配置文件,而第二步包含两个步骤,parser.parse()方法返回的是一个Configuration对象,包裹它的build方法只有一行代码:

这就可以看出,其实初始化过程就是创建Configuration对象的过程,对照MapperConfig.xml的根元素是,不难猜测到Configuration是一个非常重要的、包含了Mybatis所有数据配置的对象。

Mybatis核心对象Configuration的构建过程

接下来进入了XMLConfigBuilder.parse()方法,该方法解析XML文件的/configuration节点,然后挨个解析了上面配置文件中提到的9大配置:

我们挨个查看,这些配置项的解析,都产出了什么内容;

1、properties配置项的解析

进入propertiesElement方法,我们发现初始化了一个Properties对象,将XML中所有的子节点按照KEY-VALUE存入properties之后,和Configuration.variables变量进行了合并,而Configuration.variables本身,也是个Properties对象;

将properties配置解析后合并到Configuration.variables之后,后续的配置文件都可以使用这些变量。

2、setting的配置读取

setting配置的读取,包含两个步骤,第一步,将XML中所有的配置读取到properties对象:

这个函数读取了setting的配置项,通过反射访问Configuration.class,如果不存在某个配置项的set方法则报错;

然后在settingsElement方法中,将这些读取的配置项存入了Configuration中:

因为setting变量直接改变的是Mybatis的行为,所以配置项直接存于Confirguration的属性中。

3、typeAliases配置的解析

进入typeAliasesElement方法,用于对typeAliases配置的解析:

该方法将typeAliases的配置项提取之后,存入了typeAliasRegistry这个对象,该对象是在BaseBuilder中初始化的:

在Configuration类中,我们看到了该对象的声明:

打开该类的代码,发现特别简单的,用一个MAP存储了别名和对应的类的映射:

在构造函数中Mybatis已经默认注册了一些常用的别名和类的关系,所以我们可以在mappers的xml文件中使用这些短名字。

4、typeHandlers配置元素的解析

mybatis提供了大部分数据类型的typeHandlers,如果我们要定制自己的类型处理器比如实现数据库中0/1两个数字到中文男/女的映射,就可以自己实现typeHandler

在该方法中,通过反射得到了javaTypeClass、jdbcType、typeHandlerClass三个变量,这三个变量组成(javaType、jdbcType、typeHandler)三元组,当遇到javaType到jdbcType的转换,或者遇到jdbcType到javaType的转换时就会使用该typeHandler。

然后该方法调用了TypeHandlerRegistry.register进行注册,TypeHandlerRegistry对象是从BaseBuilder中的Configuration对象中获取的:

在TypeHandlerRegistry中,建立了几个Map映射: