Mybatis初始化过程详解

 

 

spring.xml配置如下:

 

<bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <!--配置url地址的时候需要开启useUnicode且将encoding编码定义为UTF-8 否则插入数据的时候会出现???乱码
            而&amp;是因为如果单纯以&符号做拼接的话没法识别,要以&amp;的形式转义一次 -->
        <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf-8"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
    
    <bean id="sqlSessionFactory" class ="org.mybatis.spring.SqlSessionFactoryBean">
        <!--configLocation用于配置mybatis的配置文件地址
             dataSource用于配置mybatis对应的数据源信息
             mapperLocations用于配置xml文件扫描的路径
             typeAliasesPackage对象的别名配置 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>        
        <property name="dataSource" ref="dataSource"></property>
        <!-- 该参数也可以放在mybatis-config中进行配置 所以该处省略即可
        <property name="mapperLocations" value="classpath:com/demo/dao/*Mapper.xml" ></property>
        <property name="typeAliasesPackage" value="com.demo.bean"></property>-->
    </bean>

 

 

mybatis-config.xml配置如下

 

<?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>

    <!-- 参数设置 -->
    <settings>
        <!-- 通过设置该参数可以将数据库cc_xx带下划线的参数默认映射到java属性中ccXx字段上 默认该属性为false -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 全局设置延迟加载 默认为false 如果不设置或者设置为false则全都正常加载-->
        <setting name="aggressiveLazyLoading" value="true"></setting>
        <!--全局的二级缓存的开关,默认就是开着的true。由于二级缓存是和命名空间绑定在一起的,
            想要使用这个二级缓存功能只需要开启后在每个xml文件的根节点mapper标签内加入<cache/>即可 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>

    <!-- 别名定义 -->
    <typeAliases>
        <!-- typeAlias 将type实体类重命名为country -->
        <!-- 定义别名后在mapper.xml文件中直接使用别名即可(resultType='country')
            不需要再写全称了! 
        <typeAlias alias="country" type="org.web.blog.bean.Country"></typeAlias>-->
        
        <!-- package可以取代typeAlias 
            它是通过扫描路径下所用的bean 然后通过默认规则 将bean的首字母小写作为默认别名
            也可以在bean上通过@Alias("xxx")来手动创建别名 -->
        <package name ="com.demo.bean"></package>
    </typeAliases>
    
    <!-- mysql 配置节点 可以配置多个连接节点 
        具体使用哪个节点的配置可以由default参数来定义,default与子节点的id相关联-->
    <environments default="xs">
        <!-- 正式环境节点配置 -->
        <environment id="xs">
        <!--JDBC MANAGED
            JDBC:这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。
            MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文).
                     默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。 -->
        <transactionManager type="JDBC"/>
            <!-- UNPOOLED POOLED JNDI
                UNPOOLED:Mybatis会为每一个数据库操作创建一个新的连接,并关闭它,该方式
                         适用于只有小规模数量并发用户的简单应用程序上
                POOLED:Mybatis会创建一个数据库连接池,连接池中的一个连接将会被用作数据库操作
                        一旦数据库操作完成,就会将次连接返回给连接池
                JNDI:Mybatis从在应用服务器向配置好的JNDI数据源dataSource获取数据库连接
                     生产环境优先考虑 -->
            <dataSource type="UNPOOLED">
                <property name="driver" value ="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
        <!-- 测试环境节点配置 
        <environment id="xss">
            <dataSource>
            </dataSource>
        </environment> -->
    </environments>

     <mappers>
         <!--直接映射到相应的mapper文件
        <mapper resource="com/demo/dao/UserMapper.xml"/>-->
        <!--扫描路径下java 接口文件,然后通过文件路径的全限定名作为路径去找对应的xml文件 xx.xx.java 对应xx.xx.xml
            如果xml文件与java文件的路径不一致 则映射失败,这个时候如果想单纯使用java文件 可以在接口方法上使用注解的方式来实现sql即可-->
        <package name="com.demo.dao"/>
    </mappers>
    
    
</configuration>

 

这2个文件就是Mybatis所有的配置参数的文件了,有了这两个文件Mybatis的工厂就能够初始化。

下面来说说具体的初始化过程

        //通过Reader将配置文件读入
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
        
        //FIXME 数据库初始化问题。5.0版本 drive驱动异常  6.0就不会
        //通过SqlSessionFactoryBuilder创建以Reader读取的内容为基础的SqlSessionFactory工厂类
        //在创建SqlSessionFactory工厂类创建的过程中会去解析.xml配置文件中的配置属性及mappers映射的方法信息
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        reader.close();
        
        //然后通过工厂类创建一个SqlSession,就可以使用SqlSession执行sql了
        //SqlSession中的方法是所用mybatis-config.xml映射的方法 如果这些方法中有重名的方法 就会出现方法名引用
        //如果多个xml文件中出现重名的sql方法时可以用全限定名来分别方法是属于哪个文件下的xx.xx.selectAll
        SqlSession sqlSession = sqlSessionFactory.openSession();

可以看到系统通过读取mybatis-config.xml配置文件 然后通过将读取后的参数交给SqlSessionFactory工厂进行节点解析,并创建相应的SqlSession实例。通过创建

后的SqlSession实例就可以实现sql的实现了。

 

可以看到关键的流程就是在SqlSessionFactory中得以实现,接下来看看SqlSessionFactory是如何加载这些配置参数并返回的。

它分别实现了

FactoryBean<SqlSessionFactory>,

InitializingBean,

ApplicationListener<ApplicationEvent>

这三个接口。

实现了InitializingBean 后可以保证bean在初始化的时候,bean中的afterPropertiesSet()方法得以运行

  /**
   * {@inheritDoc}
   */
  @Override
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
              "Property 'configuration' and 'configLocation' can not specified with together");

    this.sqlSessionFactory = buildSqlSessionFactory();
  }

 

可以从源码中看到引用了一个buildSqlSessionFactory()方法来初始化sqlSessionFactory。

 

下面是buildSqlSessionFactory()的源码。

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;

    XMLConfigBuilder xmlConfigBuilder = null;
  
if (this.configuration != null) { configuration = this.configuration; if (configuration.getVariables() == null) { configuration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { configuration.getVariables().putAll(this.configurationProperties); } } else if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration
= xmlConfigBuilder.getConfiguration(); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration"); } configuration = new Configuration(); if (this.configurationProperties != null) { configuration.setVariables(this.configurationProperties); } } if (this.objectFactory != null) { configuration.setObjectFactory(this.objectFactory); } if (this.objectWrapperFactory != null) { configuration.setObjectWrapperFactory(this.objectWrapperFactory); } if (this.vfs != null) { configuration.setVfsImpl(this.vfs); } if (hasLength(this.typeAliasesPackage)) { String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeAliasPackageArray) { configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases"); } } } if (!isEmpty(this.typeAliases)) { for (Class<?> typeAlias : this.typeAliases) { configuration.getTypeAliasRegistry().registerAlias(typeAlias); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered type alias: '" + typeAlias + "'"); } } } if (!isEmpty(this.plugins)) { for (Interceptor plugin : this.plugins) { configuration.addInterceptor(plugin); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered plugin: '" + plugin + "'"); } } } if (hasLength(this.typeHandlersPackage)) { String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeHandlersPackageArray) { configuration.getTypeHandlerRegistry().register(packageToScan); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers"); } } } if (!isEmpty(this.typeHandlers)) { for (TypeHandler<?> typeHandler : this.typeHandlers) { configuration.getTypeHandlerRegistry().register(typeHandler); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered type handler: '" + typeHandler + "'"); } } } if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls try { configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); } catch (SQLException e) { throw new NestedIOException("Failed getting a databaseId", e); } } if (this.cache != null) { configuration.addCache(this.cache); } if (xmlConfigBuilder != null) { try { xmlConfigBuilder.parse(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'"); } } catch (Exception ex) { throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex); } finally { ErrorContext.instance().reset(); } } if (this.transactionFactory == null) { this.transactionFactory = new SpringManagedTransactionFactory(); } configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); if (!isEmpty(this.mapperLocations)) { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'"); } } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found"); } } return this.sqlSessionFactoryBuilder.build(configuration); }

 

可以从源码中看到 SqlSessionFactory创建的最终参数是configuration。

而configuration的参数就是通过xmlConfigBuilder这个类解析xml文件并读取各个节点产生的一个初始化参数对象

比较有意思的是XMLConfigBuilder 这个类继承自 BaseBuilder,而BaseBuilder中定义了final关键字的configuration对象

从而当通过XMLConfigBuilder.getConfiguration 获得configuration对象时,这个对象也是final属性,即BaseBuilder定义的那个

所以SqlSessionFactoryBean中buildSqlSessionFactory()方法内定义的局部变量configuration 被赋值的那刻起,指向的内存地址就已经与XMLConfigBuilder中的configuration 

是一样的,也就是说这2个对象是相同的。

这个时候再通过xmlConfigBuilder.parse()方法的调用

  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);
    }
  }

可以看到是在解析xml文件中的各个标签,然后一次set到configuration 对象中去。

这个时候buildSqlSessionFactory()方法中

return this.sqlSessionFactoryBuilder.build(configuration);

这个configuration参数就已经被初始化完毕。里面包含了mybatis-confg.xml中各个标签的属性等。

 

posted @ 2018-05-10 10:29  粗鲁师太  阅读(662)  评论(0编辑  收藏  举报