Mybatis源码解读-初始化过程详解
  xCgw2s6wsXSS 2023年11月02日 92 0


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

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

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

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


Java



package                   org         .         apache         .         ibatis         .         session         ;

                  

import                   java         .         io         .         Reader         ;

import                   org         .         apache         .         ibatis         .         io         .         Resources         ;

                  

public                   class                   MyTest                   {

          public                   static                   void                   main         (         String         [         ]                   args         )                   throws                   Exception                   {

          // 开始初始化

          final                   String                   resource                   =                   "org/apache/ibatis/builder/MapperConfig.xml"         ;

          final                   Reader          reader                   =                   Resources         .         getResourceAsReader         (         resource         )         ;

          SqlSessionFactory          sqlMapper                   =                   new                   SqlSessionFactoryBuilder         (         )         .         build         (         reader         )         ;

                  

          // 开始执行SQL

          SqlSession          session                   =                   sqlMapper         .         openSession         (         )         ;

          Integer                   count                   =                   session         .         selectOne         (         "org.apache.ibatis.domain.blog.mappers.BlogMapper.selectCountOfPosts"         )         ;

          System         .         out         .         println         (         count         )         ;

          }

}



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

1、读取Mybatis的配置文件

2、构建SqlSessionFactory

3、从SqlSessionFactory中创建一个SqlSession

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

5、打印结果

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

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



XHTML

<bean          id         =         "sqlSessionFactory"                   class         =         "org.mybatis.spring.SqlSessionFactoryBean"         >

                 <property          name         =         "dataSource"                   ref         =         "dataSource"          />

                 <property          name         =         "configLocation"                   value         =         "classpath:conf/MapperConfig.xml"          />

                 <property          name         =         "mapperLocations"         >

                     <list>

                         <value>         classpath*:mapper/*.xml         </value>

                     </list>

                 </property>

             </bean>



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

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

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

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

主配置文件:MapperConfig.xml

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




XHTML


<?         xml          version         =         "1.0"                   encoding         =         "UTF-8"                   ?>

<!DOCTYPE configuration

    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"

    "http://mybatis.org/dtd/mybatis-3-config.dtd">

                  

<configuration>

                  

          <properties          resource         =         "org/apache/ibatis/databases/blog/blog-derby.properties"          />

                  

          <settings>

          <setting          name         =         "cacheEnabled"                   value         =         "true"          />

          <setting          name         =         "lazyLoadingEnabled"                   value         =         "false"          />

          <setting          name         =         "multipleResultSetsEnabled"                   value         =         "true"          />

          <setting          name         =         "useColumnLabel"                   value         =         "true"          />

          <setting          name         =         "useGeneratedKeys"                   value         =         "false"          />

          <setting          name         =         "defaultExecutorType"                   value         =         "SIMPLE"          />

          <setting          name         =         "defaultStatementTimeout"                   value         =         "25"          />

          </settings>

                  

          <typeAliases>

          <typeAlias          alias         =         "Author"                   type         =         "org.apache.ibatis.domain.blog.Author"          />

          <typeAlias          alias         =         "Blog"                   type         =         "org.apache.ibatis.domain.blog.Blog"          />

          </typeAliases>

                  

          <typeHandlers>

          <typeHandler          javaType         =         "String"                   jdbcType         =         "VARCHAR"

          handler         =         "org.apache.ibatis.builder.CustomStringTypeHandler"          />

          </typeHandlers>

                  

          <objectFactory          type         =         "org.apache.ibatis.builder.ExampleObjectFactory"         >

          <property          name         =         "objectFactoryProperty"                   value         =         "100"          />

          </objectFactory>

                  

          <plugins>

          <plugin          interceptor         =         "org.apache.ibatis.builder.ExamplePlugin"         >

          <property          name         =         "pluginProperty"                   value         =         "100"          />

          </plugin>

          </plugins>

                  

          <environments          default         =         "development"         >

          <environment          id         =         "development"         >

          <transactionManager          type         =         "JDBC"         >

          <property          name         =         ""                   value         =         ""          />

          </transactionManager>

          <dataSource          type         =         "UNPOOLED"         >

          <property          name         =         "driver"                   value         =         "${driver}"          />

          <property          name         =         "url"                   value         =         "${url}"          />

          <property          name         =         "username"                   value         =         "${username}"          />

          <property          name         =         "password"                   value         =         "${password}"          />

          </dataSource>

          </environment>

          </environments>

                  

          <databaseIdProvider          type         =         "DB_VENDOR"         >

          <property          name         =         "SQL Server"                   value         =         "sqlserver"          />

          <property          name         =         "DB2"                   value         =         "db2"          />

          <property          name         =         "Oracle"                   value         =         "oracle"          />

          </databaseIdProvider>

                  

          <mappers>

          <mapper          resource         =         "org/apache/ibatis/builder/AuthorMapper.xml"          />

          <mapper          resource         =         "org/apache/ibatis/builder/BlogMapper.xml"          />

          </mappers>

</configuration>



主配置文件只有一个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代码初始化入口





Java

@BeforeClass

          public                   static                   void                   setup         (         )                   throws                   Exception                   {

          createBlogDataSource         (         )         ;

          final                   String                   resource                   =                   "org/apache/ibatis/builder/MapperConfig.xml"         ;

          final                   Reader          reader                   =                   Resources         .         getResourceAsReader         (         resource         )         ;

          sqlMapper                   =                   new                   SqlSessionFactoryBuilder         (         )         .         build         (         reader         )         ;

          }




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

进入SqlSessionFactoryBuilder的build方法


Java

public                   SqlSessionFactory          build         (         Reader          reader         ,                   String                   environment         ,                   Properties          properties         )                   {

          try                   {

          XMLConfigBuilder          parser                   =                   new                   XMLConfigBuilder         (         reader         ,                   environment         ,                   properties         )         ;

          return                   build         (         parser         .         parse         (         )         )         ;

          }                   catch                   (         Exception                   e         )                   {

          throw                   ExceptionFactory         .         wrapException         (         "Error building SqlSession."         ,                   e         )         ;

          }                   finally                   {

          ErrorContext         .         instance         (         )         .         reset         (         )         ;

          try                   {

          reader         .         close         (         )         ;

          }                   catch                   (         IOException                   e         )                   {

          // Intentionally ignore. Prefer previous error.

          }

          }

          }




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



Java

public                   SqlSessionFactory          build         (         Configuration          config         )                   {

          return                   new                   DefaultSqlSessionFactory         (         config         )         ;

          }





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

Mybatis核心对象Configuration的构建过程

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


Java

public                   Configuration          parse         (         )                   {

          if                   (         parsed         )                   {

          throw                   new                   BuilderException         (         "Each XMLConfigBuilder can only be used once."         )         ;

          }

          parsed                   =                   true         ;

          parseConfiguration         (         parser         .         evalNode         (         "/configuration"         )         )         ;

          return                   configuration         ;

          }

                  

          private                   void                   parseConfiguration         (         XNode          root         )                   {

          try                   {

          // issue #117 read properties first

          propertiesElement         (         root         .         evalNode         (         "properties"         )         )         ;

          Properties          settings                   =                   settingsAsProperties         (         root         .         evalNode         (         "settings"         )         )         ;

          loadCustomVfs         (         settings         )         ;

          typeAliasesElement         (         root         .         evalNode         (         "typeAliases"         )         )         ;

          pluginElement         (         root         .         evalNode         (         "plugins"         )         )         ;

          objectFactoryElement         (         root         .         evalNode         (         "objectFactory"         )         )         ;

          objectWrapperFactoryElement         (         root         .         evalNode         (         "objectWrapperFactory"         )         )         ;

          reflectorFactoryElement         (         root         .         evalNode         (         "reflectorFactory"         )         )         ;

          settingsElement         (         settings         )         ;

          // read it after objectFactory and objectWrapperFactory issue #631

          environmentsElement         (         root         .         evalNode         (         "environments"         )         )         ;

          databaseIdProviderElement         (         root         .         evalNode         (         "databaseIdProvider"         )         )         ;

          typeHandlerElement         (         root         .         evalNode         (         "typeHandlers"         )         )         ;

          mapperElement         (         root         .         evalNode         (         "mappers"         )         )         ;

          }                   catch                   (         Exception                   e         )                   {

          throw                   new                   BuilderException         (         "Error parsing SQL Mapper Configuration. Cause: "                   +                   e         ,                   e         )         ;

          }

          }




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

1、properties配置项的解析

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








Java

private                   void                   propertiesElement         (         XNode          context         )                   throws                   Exception                   {

          if                   (         context                   !=                   null         )                   {

          Properties          defaults                   =                   context         .         getChildrenAsProperties         (         )         ;

          String                   resource                   =                   context         .         getStringAttribute         (         "resource"         )         ;

          String                   url                   =                   context         .         getStringAttribute         (         "url"         )         ;

          if                   (         resource                   !=                   null                   &&                   url                   !=                   null         )                   {

          throw                   new                   BuilderException         (

          "The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other."         )         ;

          }

          if                   (         resource                   !=                   null         )                   {

          defaults         .         putAll         (         Resources         .         getResourceAsProperties         (         resource         )         )         ;

          }                   else                   if                   (         url                   !=                   null         )                   {

          defaults         .         putAll         (         Resources         .         getUrlAsProperties         (         url         )         )         ;

          }

          Properties          vars                   =                   configuration         .         getVariables         (         )         ;

          if                   (         vars                   !=                   null         )                   {

          defaults         .         putAll         (         vars         )         ;

          }

          parser         .         setVariables         (         defaults         )         ;

          configuration         .         setVariables         (         defaults         )         ;

          }

          }



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

2、setting的配置读取

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


Java

private                   Properties          settingsAsProperties         (         XNode          context         )                   {

          if                   (         context                   ==                   null         )                   {

          return                   new                   Properties         (         )         ;

          }

          Properties          props                   =                   context         .         getChildrenAsProperties         (         )         ;

          // Check that all settings are known to the configuration class

          MetaClass          metaConfig                   =                   MetaClass         .         forClass         (         Configuration         .         class         ,                   localReflectorFactory         )         ;

          for                   (         Object                   key                   :                   props         .         keySet         (         )         )                   {

          if                   (         !         metaConfig         .         hasSetter         (         String         .         valueOf         (         key         )         )         )                   {

          throw                   new                   BuilderException         (

          "The setting "                   +                   key                   +                   " is not known.  Make sure you spelled it correctly (case sensitive)."         )         ;

          }

          }

          return                   props         ;

          }



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

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


Java

private                   void                   settingsElement         (         Properties          props         )                   throws                   Exception                   {

          configuration         .         setAutoMappingBehavior         (

          AutoMappingBehavior         .         valueOf         (         props         .         getProperty         (         "autoMappingBehavior"         ,                   "PARTIAL"         )         )         )         ;

          configuration         .         setAutoMappingUnknownColumnBehavior         (         AutoMappingUnknownColumnBehavior

          .         valueOf         (         props         .         getProperty         (         "autoMappingUnknownColumnBehavior"         ,                   "NONE"         )         )         )         ;

          configuration         .         setCacheEnabled         (         booleanValueOf         (         props         .         getProperty         (         "cacheEnabled"         )         ,                   true         )         )         ;

          configuration         .         setProxyFactory         (         (         ProxyFactory         )                   createInstance         (         props         .         getProperty         (         "proxyFactory"         )         )         )         ;

          configuration         .         setLazyLoadingEnabled         (         booleanValueOf         (         props         .         getProperty         (         "lazyLoadingEnabled"         )         ,                   false         )         )         ;

          configuration         .         setAggressiveLazyLoading         (         booleanValueOf         (         props         .         getProperty         (         "aggressiveLazyLoading"         )         ,                   false         )         )         ;

          configuration

          .         setMultipleResultSetsEnabled         (         booleanValueOf         (         props         .         getProperty         (         "multipleResultSetsEnabled"         )         ,                   true         )         )         ;

          configuration         .         setUseColumnLabel         (         booleanValueOf         (         props         .         getProperty         (         "useColumnLabel"         )         ,                   true         )         )         ;

          configuration         .         setUseGeneratedKeys         (         booleanValueOf         (         props         .         getProperty         (         "useGeneratedKeys"         )         ,                   false         )         )         ;

          configuration         .         setDefaultExecutorType         (         ExecutorType         .         valueOf         (         props         .         getProperty         (         "defaultExecutorType"         ,                   "SIMPLE"         )         )         )         ;

          configuration         .         setDefaultStatementTimeout         (         integerValueOf         (         props         .         getProperty         (         "defaultStatementTimeout"         )         ,                   null         )         )         ;

          configuration         .         setDefaultFetchSize         (         integerValueOf         (         props         .         getProperty         (         "defaultFetchSize"         )         ,                   null         )         )         ;

          configuration         .         setMapUnderscoreToCamelCase         (         booleanValueOf         (         props         .         getProperty         (         "mapUnderscoreToCamelCase"         )         ,                   false         )         )         ;

          configuration         .         setSafeRowBoundsEnabled         (         booleanValueOf         (         props         .         getProperty         (         "safeRowBoundsEnabled"         )         ,                   false         )         )         ;

          configuration         .         setLocalCacheScope         (         LocalCacheScope         .         valueOf         (         props         .         getProperty         (         "localCacheScope"         ,                   "SESSION"         )         )         )         ;

          configuration         .         setJdbcTypeForNull         (         JdbcType         .         valueOf         (         props         .         getProperty         (         "jdbcTypeForNull"         ,                   "OTHER"         )         )         )         ;

          configuration         .         setLazyLoadTriggerMethods         (

          stringSetValueOf         (         props         .         getProperty         (         "lazyLoadTriggerMethods"         )         ,                   "equals,clone,hashCode,toString"         )         )         ;

          configuration         .         setSafeResultHandlerEnabled         (         booleanValueOf         (         props         .         getProperty         (         "safeResultHandlerEnabled"         )         ,                   true         )         )         ;

          configuration         .         setDefaultScriptingLanguage         (         resolveClass         (         props         .         getProperty         (         "defaultScriptingLanguage"         )         )         )         ;

          configuration         .         setCallSettersOnNulls         (         booleanValueOf         (         props         .         getProperty         (         "callSettersOnNulls"         )         ,                   false         )         )         ;

          configuration         .         setUseActualParamName         (         booleanValueOf         (         props         .         getProperty         (         "useActualParamName"         )         ,                   true         )         )         ;

          configuration

          .         setReturnInstanceForEmptyRow         (         booleanValueOf         (         props         .         getProperty         (         "returnInstanceForEmptyRow"         )         ,                   false         )         )         ;

          configuration         .         setLogPrefix         (         props         .         getProperty         (         "logPrefix"         )         )         ;

          @SuppressWarnings         (         "unchecked"         )

          Class         <         ?                   extends                   Log         >                   logImpl                   =                   (         Class         <         ?                   extends                   Log         >         )                   resolveClass         (         props         .         getProperty         (         "logImpl"         )         )         ;

          configuration         .         setLogImpl         (         logImpl         )         ;

          configuration         .         setConfigurationFactory         (         resolveClass         (         props         .         getProperty         (         "configurationFactory"         )         )         )         ;

          }




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

3、typeAliases配置的解析

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



Java

private                   void                   typeAliasesElement         (         XNode          parent         )                   {

          if                   (         parent                   !=                   null         )                   {

          for                   (         XNode          child                   :                   parent         .         getChildren         (         )         )                   {

          if                   (         "package"         .         equals         (         child         .         getName         (         )         )         )                   {

          String                   typeAliasPackage                   =                   child         .         getStringAttribute         (         "name"         )         ;

          configuration         .         getTypeAliasRegistry         (         )         .         registerAliases         (         typeAliasPackage         )         ;

          }                   else                   {

          String                   alias                   =                   child         .         getStringAttribute         (         "alias"         )         ;

          String                   type                   =                   child         .         getStringAttribute         (         "type"         )         ;

          try                   {

          Class         <         ?         >                   clazz                   =                   Resources         .         classForName         (         type         )         ;

          if                   (         alias                   ==                   null         )                   {

          typeAliasRegistry         .         registerAlias         (         clazz         )         ;

          }                   else                   {

          typeAliasRegistry         .         registerAlias         (         alias         ,                   clazz         )         ;

          }

          }                   catch                   (         ClassNotFoundException                   e         )                   {

          throw                   new                   BuilderException         (         "Error registering typeAlias for '"                   +                   alias                   +                   "'. Cause: "                   +                   e         ,                   e         )         ;

          }

          }

          }

          }

          }



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



Java

public                   abstract                   class                   BaseBuilder                   {

          protected                   final                   Configuration          configuration         ;

          protected                   final                   TypeAliasRegistry          typeAliasRegistry         ;

          protected                   final                   TypeHandlerRegistry          typeHandlerRegistry         ;

                  

          public                   BaseBuilder         (         Configuration          configuration         )                   {

          this         .         configuration                   =                   configuration         ;

          this         .         typeAliasRegistry                   =                   this         .         configuration         .         getTypeAliasRegistry         (         )         ;

          this         .         typeHandlerRegistry                   =                   this         .         configuration         .         getTypeHandlerRegistry         (         )         ;

          }



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


Java

protected                   final                   TypeAliasRegistry          typeAliasRegistry                   =                   new                   TypeAliasRegistry         (         )         ;




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


Java


public                   class                   TypeAliasRegistry                   {

                  

          private                   final                   Map         <         String         ,                   Class         <?         >>                   TYPE_ALIASES                   =                   new                   HashMap         <         String         ,                   Class         <?         >         >         (         )         ;

                  

          public                   TypeAliasRegistry         (         )                   {

          registerAlias         (         "string"         ,                   String         .         class         )         ;

                  

          registerAlias         (         "byte"         ,                   Byte         .         class         )         ;

          registerAlias         (         "long"         ,                   Long         .         class         )         ;

          registerAlias         (         "short"         ,                   Short         .         class         )         ;

          registerAlias         (         "int"         ,                   Integer         .         class         )         ;

          registerAlias         (         "integer"         ,                   Integer         .         class         )         ;

          registerAlias         (         "double"         ,                   Double         .         class         )         ;

          registerAlias         (         "float"         ,                   Float         .         class         )         ;

          registerAlias         (         "boolean"         ,                   Boolean         .         class         )         ;



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

4、typeHandlers配置元素的解析

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




Java


private                   void                   typeHandlerElement         (         XNode          parent         )                   throws                   Exception                   {

          if                   (         parent                   !=                   null         )                   {

          for                   (         XNode          child                   :                   parent         .         getChildren         (         )         )                   {

          if                   (         "package"         .         equals         (         child         .         getName         (         )         )         )                   {

          String                   typeHandlerPackage                   =                   child         .         getStringAttribute         (         "name"         )         ;

          typeHandlerRegistry         .         register         (         typeHandlerPackage         )         ;

          }                   else                   {

          String                   javaTypeName                   =                   child         .         getStringAttribute         (         "javaType"         )         ;

          String                   jdbcTypeName                   =                   child         .         getStringAttribute         (         "jdbcType"         )         ;

          String                   handlerTypeName                   =                   child         .         getStringAttribute         (         "handler"         )         ;

          Class         <?         >                   javaTypeClass                   =                   resolveClass         (         javaTypeName         )         ;

          JdbcType          jdbcType                   =                   resolveJdbcType         (         jdbcTypeName         )         ;

          Class         <?         >                   typeHandlerClass                   =                   resolveClass         (         handlerTypeName         )         ;

          if                   (         javaTypeClass                   !=                   null         )                   {

          if                   (         jdbcType                   ==                   null         )                   {

          typeHandlerRegistry         .         register         (         javaTypeClass         ,                   typeHandlerClass         )         ;

          }                   else                   {

          typeHandlerRegistry         .         register         (         javaTypeClass         ,                   jdbcType         ,                   typeHandlerClass         )         ;

          }

          }                   else                   {

          typeHandlerRegistry         .         register         (         typeHandlerClass         )         ;

          }

          }

          }

          }

          }



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

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




Java


public                   abstract                   class                   BaseBuilder                   {

          protected                   final                   Configuration          configuration         ;

          protected                   final                   TypeAliasRegistry          typeAliasRegistry         ;

          protected                   final                   TypeHandlerRegistry          typeHandlerRegistry         ;

                  

          public                   BaseBuilder         (         Configuration          configuration         )                   {

          this         .         configuration                   =                   configuration         ;

          this         .         typeAliasRegistry                   =                   this         .         configuration         .         getTypeAliasRegistry         (         )         ;

          this         .         typeHandlerRegistry                   =                   this         .         configuration         .         getTypeHandlerRegistry         (         )         ;

          }



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



Java


public                   final                   class                   TypeHandlerRegistry                   {

                  

          private                   final                   Map         <         JdbcType         ,                   TypeHandler         <?         >>                   JDBC_TYPE_HANDLER_MAP                   =                   new                   EnumMap         <         JdbcType         ,                   TypeHandler         <?         >         >         (

          JdbcType         .         class         )         ;

          private                   final                   Map         <         Type         ,                   Map         <         JdbcType         ,                   TypeHandler         <?         >>>                   TYPE_HANDLER_MAP                   =                   new                   HashMap         <         Type         ,                   Map         <         JdbcType         ,                   TypeHandler         <?         >         >>         (         )         ;

          private                   final                   TypeHandler         <Object>                   UNKNOWN_TYPE_HANDLER                   =                   new                   UnknownTypeHandler         (         this         )         ;

          private                   final                   Map         <         Class         <?         >         ,                   TypeHandler         <?         >         >                   ALL_TYPE_HANDLERS_MAP                   =                   new                   HashMap         <         Class         <?         >         ,                   TypeHandler         <?         >         >         (         )         ;



第一个是JdbcType为key的map,第二个是JavaType为key的map,第三个是未知的处理器、最后一个是包含全部的处理器;

当执行SQL的时候,会将javaBean的JavaType转换到DB的jdbcType,而查询出来数据的时候,又需要将jdbcType转换成javaType,在TypeHandlerRegistry的构造函数中,已经注册好了很多默认的typeHandler,大部分情况下不需要我们添加:



Java

public                   TypeHandlerRegistry         (         )                   {

          register         (         Boolean         .         class         ,                   new                   BooleanTypeHandler         (         )         )         ;

          register         (         boolean         .         class         ,                   new                   BooleanTypeHandler         (         )         )         ;

          register         (         JdbcType         .         BOOLEAN         ,                   new                   BooleanTypeHandler         (         )         )         ;

          register         (         JdbcType         .         BIT         ,                   new                   BooleanTypeHandler         (         )         )         ;

                  

          register         (         Byte         .         class         ,                   new                   ByteTypeHandler         (         )         )         ;

          register         (         byte         .         class         ,                   new                   ByteTypeHandler         (         )         )         ;

          register         (         JdbcType         .         TINYINT         ,                   new                   ByteTypeHandler         (         )         )         ;

                  

          register         (         Short         .         class         ,                   new                   ShortTypeHandler         (         )         )         ;

          register         (         short         .         class         ,                   new                   ShortTypeHandler         (         )         )         ;

          register         (         JdbcType         .         SMALLINT         ,                   new                   ShortTypeHandler         (         )         )         ;



要实现一个typeHandler,需要实现接口,该接口提供的就是从javaType到jdbcType的setParameter方法,以及从jdbcType到javaType转换的getResult方法:



Java


public                   interface                   TypeHandler         <T>                   {

                  

          void                   setParameter         (         PreparedStatement          ps         ,                   int                   i         ,                   T                   parameter         ,                   JdbcType          jdbcType         )                   throws                   SQLException         ;

                  

          T                   getResult         (         ResultSet          rs         ,                   String                   columnName         )                   throws                   SQLException         ;

                  

          T                   getResult         (         ResultSet          rs         ,                   int                   columnIndex         )                   throws                   SQLException         ;

                  

          T                   getResult         (         CallableStatement          cs         ,                   int                   columnIndex         )                   throws                   SQLException         ;

                  

}



 

5、objectFactory配置项的解析

如果想自己控制查询数据库的结果到JavaBean映射的生成,则可以创建自己的objectFactory,解析代码如下:



Java

private                   void                   objectFactoryElement         (         XNode          context         )                   throws                   Exception                   {

          if                   (         context                   !=                   null         )                   {

          String                   type                   =                   context         .         getStringAttribute         (         "type"         )         ;

          Properties          properties                   =                   context         .         getChildrenAsProperties         (         )         ;

          ObjectFactory          factory                   =                   (         ObjectFactory         )                   resolveClass         (         type         )         .         newInstance         (         )         ;

          factory         .         setProperties         (         properties         )         ;

          configuration         .         setObjectFactory         (         factory         )         ;

          }

          }



可以看到,该配置项包含type属性,以及properties子节点,创建好ObjectFactory对象后,就会设置到configuration中:




Java


// Configuration对象的objectFactory成员变量

protected                   ObjectFactory          objectFactory                   =                   new                   DefaultObjectFactory         (         )         ;



要实现ObjectFactory,需要继承该接口:




Java

public                   interface                   ObjectFactory                   {

          void                   setProperties         (         Properties          properties         )         ;

          <T>                   T                   create         (         Class         <T>                   type         )         ;

          <T>                   T                   create         (         Class         <T>                   type         ,                   List         <         Class         <         ?         >>                   constructorArgTypes         ,                   List         <Object>                   constructorArgs         )         ;

          <T>                   boolean                   isCollection         (         Class         <T>                   type         )         ;

}





该工厂接口提供了设置属性列表,还有创建对象的工厂方法。

6、plugin元素的解析

plugin,即mybatis的插件,可以让我们自己进行开发用于扩展mybatis。

进入pluginElement方法进入解析:



Java

private                   void                   pluginElement         (         XNode          parent         )                   throws                   Exception                   {

          if                   (         parent                   !=                   null         )                   {

          for                   (         XNode          child                   :                   parent         .         getChildren         (         )         )                   {

          String                   interceptor                   =                   child         .         getStringAttribute         (         "interceptor"         )         ;

          Properties          properties                   =                   child         .         getChildrenAsProperties         (         )         ;

          Interceptor          interceptorInstance                   =                   (         Interceptor         )                   resolveClass         (         interceptor         )         .         newInstance         (         )         ;

          interceptorInstance         .         setProperties         (         properties         )         ;

          configuration         .         addInterceptor         (         interceptorInstance         )         ;

          }

          }

          }



该段代码,首先获取intercepter元素作为拦截器,然后读取该节点的所有子节点作为配置项,最后调用configuration.addInterceptor方法添加到了configuration中的interceptorChain中,该对象是拦截器链的一个包装对象:



Java

public                   class                   InterceptorChain                   {

                  

          private                   final                   List         <Interceptor>                   interceptors                   =                   new                   ArrayList         <Interceptor>         (         )         ;

                  

          public                   Object                   pluginAll         (         Object                   target         )                   {

          for                   (         Interceptor          interceptor                   :                   interceptors         )                   {

                                 // target变量每次也在变化着

          target                   =                   interceptor         .         plugin         (         target         )         ;

          }

          return                   target         ;

          }

                  

          public                   void                   addInterceptor         (         Interceptor          interceptor         )                   {

          interceptors         .         add         (         interceptor         )         ;

          }

                  

          public                   List         <Interceptor>                   getInterceptors         (         )                   {

          return                   Collections         .         unmodifiableList         (         interceptors         )         ;

          }

                  

}


该类中使用List<Interceptor>存储了所有配置的拦截器,并提供了addInterceptor用于添加拦截器,提供了getInterceptors用于获取当前所有添加的插件列表,提供了pluginAll接口调用所有的Interceptor.plugin(Object)方法进行插件的执行。

7、environments的配置解析

environments可以配置多个环境配置,每个配置包含了数据源和事务管理器两项,如下所示代码:




Java



private                   void                   environmentsElement         (         XNode          context         )                   throws                   Exception                   {

          if                   (         context                   !=                   null         )                   {

          if                   (         environment                   ==                   null         )                   {

          environment                   =                   context         .         getStringAttribute         (         "default"         )         ;

          }

          for                   (         XNode          child                   :                   context         .         getChildren         (         )         )                   {

          String                   id                   =                   child         .         getStringAttribute         (         "id"         )         ;

          if                   (         isSpecifiedEnvironment         (         id         )         )                   {

          TransactionFactory          txFactory                   =                   transactionManagerElement         (         child         .         evalNode         (         "transactionManager"         )         )         ;

          DataSourceFactory          dsFactory                   =                   dataSourceElement         (         child         .         evalNode         (         "dataSource"         )         )         ;

          DataSource          dataSource                   =                   dsFactory         .         getDataSource         (         )         ;

          Environment         .         Builder          environmentBuilder                   =                   new                   Environment         .         Builder         (         id         )         .         transactionFactory         (         txFactory         )

          .         dataSource         (         dataSource         )         ;

          configuration         .         setEnvironment         (         environmentBuilder         .         build         (         )         )         ;

          }

          }

          }

          }



代码中通过isSpecifiedEnvironment方法判断当前的id是不是指定要读取的environment,如果是的话通过反射获取事务管理器和数据源,然后用Environment.Builder创建Enviroment对象并设置到Configuration中,在Configuration中可以看到Enviroment成员变量:




Java


protected Environment environment;


而Enviroment对象也只包含了这三个属性:








Java


public                   final                   class                   Environment                   {

          private                   final                   String                   id         ;

          private                   final                   TransactionFactory          transactionFactory         ;

          private                   final                   DataSource          dataSource         ;



8、databaseIdProvider配置项的解析

mybatis当然不只是支持mysql,也会支持oracle、sqlserver等不同的数据库,解析代码如下:



Java

private                   void                   databaseIdProviderElement         (         XNode          context         )                   throws                   Exception                   {

          DatabaseIdProvider          databaseIdProvider                   =                   null         ;

          if                   (         context                   !=                   null         )                   {

          String                   type                   =                   context         .         getStringAttribute         (         "type"         )         ;

          // awful patch to keep backward compatibility

          if                   (         "VENDOR"         .         equals         (         type         )         )                   {

          type                   =                   "DB_VENDOR"         ;

          }

          Properties          properties                   =                   context         .         getChildrenAsProperties         (         )         ;

          databaseIdProvider                   =                   (         DatabaseIdProvider         )                   resolveClass         (         type         )         .         newInstance         (         )         ;

          databaseIdProvider         .         setProperties         (         properties         )         ;

          }

          Environment          environment                   =                   configuration         .         getEnvironment         (         )         ;

          if                   (         environment                   !=                   null                   &&                   databaseIdProvider                   !=                   null         )                   {

          String                   databaseId                   =                   databaseIdProvider         .         getDatabaseId         (         environment         .         getDataSource         (         )         )         ;

          configuration         .         setDatabaseId         (         databaseId         )         ;

          }

          }




解析databaseIdProvider后里面的Properties中存储了各种数据库的映射,并且databaseIdProvider提供了一个根据dataSource获取对应的databseId的方法,以VendorDatabaseIdProvider为例,是通过connection.getMetaData().getDatabaseProductName()获取数据库的产品名称,然后从刚才databaseIdProvider中获取对应的databaseId:




Java


public                   class                   VendorDatabaseIdProvider                   implements                   DatabaseIdProvider                   {

                  

          private                   static                   final                   Log          log                   =                   LogFactory         .         getLog         (         VendorDatabaseIdProvider         .         class         )         ;

                  

          private                   Properties          properties         ;

                  

          @Override

          public                   String                   getDatabaseId         (         DataSource          dataSource         )                   {

          if                   (         dataSource                   ==                   null         )                   {

          throw                   new                   NullPointerException         (         "dataSource cannot be null"         )         ;

          }

          try                   {

          return                   getDatabaseName         (         dataSource         )         ;

          }                   catch                   (         Exception                   e         )                   {

          log         .         error         (         "Could not get a databaseId from dataSource"         ,                   e         )         ;

          }

          return                   null         ;

          }

                  

          @Override

          public                   void                   setProperties         (         Properties                   p         )                   {

          this         .         properties                   =                   p         ;

          }

                  

          private                   String                   getDatabaseName         (         DataSource          dataSource         )                   throws                   SQLException                   {

          String                   productName                   =                   getDatabaseProductName         (         dataSource         )         ;

          if                   (         this         .         properties                   !=                   null         )                   {

          for                   (         Map         .         Entry         <         Object         ,                   Object         >                   property                   :                   properties         .         entrySet         (         )         )                   {

          if                   (         productName         .         contains         (         (         String         )                   property         .         getKey         (         )         )         )                   {

          return                   (         String         )                   property         .         getValue         (         )         ;

          }

          }

          // no match, return null

          return                   null         ;

          }

          return                   productName         ;

          }

                  

          private                   String                   getDatabaseProductName         (         DataSource          dataSource         )                   throws                   SQLException                   {

          Connection          con                   =                   null         ;

          try                   {

          con                   =                   dataSource         .         getConnection         (         )         ;

          DatabaseMetaData          metaData                   =                   con         .         getMetaData         (         )         ;

          return                   metaData         .         getDatabaseProductName         (         )         ;

          }                   finally                   {

          if                   (         con                   !=                   null         )                   {

          try                   {

          con         .         close         (         )         ;

          }                   catch                   (         SQLException                   e         )                   {

          // ignored

          }

          }

          }

          }

                  

}



在获取了databaseId之后,最后将databaseId设置到configuration,后续当执行SQL的时候会自动根据该databaseId来映射具体数据库的SQL。

9、mappers配置项的解析

mappers的解析最为复杂,我们假设mapper文件均是url指定的xml文件,来进行解析流程的查看:



Java


private                   void                   mapperElement         (         XNode          parent         )                   throws                   Exception                   {

          if                   (         parent                   !=                   null         )                   {

          for                   (         XNode          child                   :                   parent         .         getChildren         (         )         )                   {

          if                   (         "package"         .         equals         (         child         .         getName         (         )         )         )                   {

          String                   mapperPackage                   =                   child         .         getStringAttribute         (         "name"         )         ;

          configuration         .         addMappers         (         mapperPackage         )         ;

          }                   else                   {

          String                   resource                   =                   child         .         getStringAttribute         (         "resource"         )         ;

          String                   url                   =                   child         .         getStringAttribute         (         "url"         )         ;

          String                   mapperClass                   =                   child         .         getStringAttribute         (         "class"         )         ;

          if                   (         resource                   !=                   null                   &&                   url                   ==                   null                   &&                   mapperClass                   ==                   null         )                   {

          ErrorContext         .         instance         (         )         .         resource         (         resource         )         ;

          InputStream          inputStream                   =                   Resources         .         getResourceAsStream         (         resource         )         ;



          // 使用XMLMapperBuilder加载mapper.xml,然后进入parse()方法

          XMLMapperBuilder          mapperParser                   =                   new                   XMLMapperBuilder         (         inputStream         ,                   configuration         ,                   resource         ,

          configuration         .         getSqlFragments         (         )         )         ;

          mapperParser         .         parse         (         )         ;

          }                   else                   if                   (         resource                   ==                   null                   &&                   url                   !=                   null                   &&                   mapperClass                   ==                   null         )                   {

          ErrorContext         .         instance         (         )         .         resource         (         url         )         ;

          InputStream          inputStream                   =                   Resources         .         getUrlAsStream         (         url         )         ;

          XMLMapperBuilder          mapperParser                   =                   new                   XMLMapperBuilder         (         inputStream         ,                   configuration         ,                   url         ,

          configuration         .         getSqlFragments         (         )         )         ;

          mapperParser         .         parse         (         )         ;

          }                   else                   if                   (         resource                   ==                   null                   &&                   url                   ==                   null                   &&                   mapperClass                   !=                   null         )                   {

          Class         <         ?         >                   mapperInterface                   =                   Resources         .         classForName         (         mapperClass         )         ;

          configuration         .         addMapper         (         mapperInterface         )         ;

          }                   else                   {

          throw                   new                   BuilderException         (

          "A mapper element may only specify a url, resource or class, but not more than one."         )         ;

          }

          }

          }

          }

          }



加注释部分显示,读取每个mapper.xml资源文件的地址后,进入了XMLMapperBuilder.parse()方法:




Java

public                   void                   parse         (         )                   {

          if                   (         !         configuration         .         isResourceLoaded         (         resource         )         )                   {

          configurationElement         (         parser         .         evalNode         (         "/mapper"         )         )         ;

          configuration         .         addLoadedResource         (         resource         )         ;

          bindMapperForNamespace         (         )         ;

          }

                  

          parsePendingResultMaps         (         )         ;

          parsePendingChacheRefs         (         )         ;

          parsePendingStatements         (         )         ;

          }

                  

          private                   void                   configurationElement         (         XNode          context         )                   {

          try                   {

          String                   namespace                   =                   context         .         getStringAttribute         (         "namespace"         )         ;

          if                   (         namespace                   ==                   null                   ||                   namespace         .         equals         (         ""         )         )                   {

          throw                   new                   BuilderException         (         "Mapper's namespace cannot be empty"         )         ;

          }

          builderAssistant         .         setCurrentNamespace         (         namespace         )         ;

          cacheRefElement         (         context         .         evalNode         (         "cache-ref"         )         )         ;

          cacheElement         (         context         .         evalNode         (         "cache"         )         )         ;

          parameterMapElement         (         context         .         evalNodes         (         "/mapper/parameterMap"         )         )         ;

          resultMapElements         (         context         .         evalNodes         (         "/mapper/resultMap"         )         )         ;

          sqlElement         (         context         .         evalNodes         (         "/mapper/sql"         )         )         ;

          buildStatementFromContext         (         context         .         evalNodes         (         "select|insert|update|delete"         )         )         ;

          }                   catch                   (         Exception                   e         )                   {

          throw                   new                   BuilderException         (         "Error parsing Mapper XML. Cause: "                   +                   e         ,                   e         )         ;

          }

          }



然后进入了configurationElement(parser.evalNode(“/mapper”));方法后会读取所有xml中mapper下的子元素,在这里我们只查看buildStatementFromContext方法:


Java

private                   void                   buildStatementFromContext         (         List         <XNode>                   list         )                   {

          if                   (         configuration         .         getDatabaseId         (         )                   !=                   null         )                   {

          buildStatementFromContext         (         list         ,                   configuration         .         getDatabaseId         (         )         )         ;

          }

          buildStatementFromContext         (         list         ,                   null         )         ;

          }

                  

          private                   void                   buildStatementFromContext         (         List         <XNode>                   list         ,                   String                   requiredDatabaseId         )                   {

          for                   (         XNode          context                   :                   list         )                   {

          final                   XMLStatementBuilder          statementParser                   =                   new                   XMLStatementBuilder         (         configuration         ,                   builderAssistant         ,

          context         ,                   requiredDatabaseId         )         ;

          try                   {

          statementParser         .         parseStatementNode         (         )         ;

          }                   catch                   (         IncompleteElementException                   e         )                   {

          configuration         .         addIncompleteStatement         (         statementParser         )         ;

          }

          }

          }



该方法出现了一个XMLStatementBuilder用于select/insert/update/delete语句各自的解析,在XMLStatementBuilder.parseStatementNode方法中解析了各种语句的属性和参数以及动态SQL的处理,最后调用builderAssistant.addMappedStatement方法,所有的参数和内容被构建成MappedStatement,添加到了configuration中:




Java

MappedStatement          statement                   =                   statementBuilder         .         build         (         )         ;

configuration         .         addMappedStatement         (         statement         )         ;


以下是configuration中的mappedStatements对象:




Java


// Configuration的mappedStatements对象

protected                   final                   Map         <         String         ,                   MappedStatement         >                   mappedStatements                   =                   new                   StrictMap         <MappedStatement>         (

          "Mapped Statements collection"         )         ;



这里的StrictMap就是一个HashMap,在addMappedStatement方法中可以看到该map的Key是各个SQL的ID:




Java

public                   void                   addMappedStatement         (         MappedStatement          ms         )                   {

          mappedStatements         .         put         (         ms         .         getId         (         )         ,                   ms         )         ;

          }





由此可以推测整个Mybatis执行SQL的过程:

  1. 业务代码调用SqlMapperInterface.method方法;
  2. Mybatis根据method的全限定名称作为ID,从mappedStatements找到构建好的MappedStatement对象;
  3. 使用MappedStatement中读取的SQL等各种配置,执行SQL;

Mybatis初始化的对象产出列表总结

以上就是对Mybatis初始化过程的详解,其最终产出了以下对象列表:

  1. 总产出:SqlSessionFactory,相当于ConnectionFactory用于生产SqlSession,而SqlSession相当于Connection用于实际的SQL查询;
  2. SQlSessionFactory的核心对象Configuration,所有的Mybatis配置项和Mapper配置列表,都会被解析并读取到该对象的属性中;
  3. Configuration中的各个对象:
  • Configuration.variables,类型为Properties,存储Mybatis配置的Properties对象;
  • Configuration.直接属性,setting的配置,因为直接影响Mybatis的行为,直接赋值到了Configuration对象的属性;
  • Configuration.TypeAliasRegistry对象,存储别名映射,KEY是别名,VALUE是别名对应的类;
  • Configuration.TypeHandlerRegistry对象,存储typeHandler映射,KEY是javaType或者jdbcType,VALUE是typeHandler类;
  • Configuration.ObjectFactory对象,存储单个的ObjectFactory对象,用于DB映射JAVABEAN对象的创建;
  • Configuration.InterceptorChain对象,存储Plugin对象列表,用户可以添加用于扩展Mybatis;
  • Configuration.Environment对象,指定开发/测试/线上环境,根据其dataSource也决定了databaseId对象的取值;
  • Configuration.databaseId,存储使用的DB的类型,Mybatis会根据不同的DB做SQL适配;
  • Configuration.mappedStatements,存储KEY为ID,VALUE为MappedStatement的MAP,执行SQL时从此处获取对象;

参考资料:


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
xCgw2s6wsXSS