天天看點

mybatis源碼解析(一)SqlSessionFactorySqlSessionFactory

SqlSessionFactory

在使用mybatis時,我們一般會與spring進行內建,在spring配置檔案中(子容器)配置sqlSessionFactory如下:

注意:一般在配置spring項目時,我們都是根據經驗配置,但其實spring與spirngmvc的上下文(上下文的概念等價于容器)是有父子上下文的含義。

(關于spring和spirngmvc父子容器上下文的關系,請參考https://blog.csdn.net/chuixue24/article/details/103820840)

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
    <property name="typeAliasesPackage">
        <array>
            <value>com.chuixue.test.entity</value>
        </array>
    </property>        
</bean>
           

sqlSessionFactory的類型為org.mybatis.spring.SqlSessionFactoryBean,檢視其源碼,可以發現其實作了幾個spring提供的接口:

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>,
 InitializingBean, ApplicationListener<ApplicationEvent> {
    //代碼省略
}
           

FactoryBean接口比較簡單,隻有三個方法:

public interface FactoryBean<T> {

	
	T getObject() throws Exception;

	
	Class<?> getObjectType();

	
	boolean isSingleton();

}
           

org.mybatis.spring.SqlSessionFactoryBean實作如下:

/**
   * {@inheritDoc}
   */
  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }

    return this.sqlSessionFactory;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Class<? extends SqlSessionFactory> getObjectType() {
    return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isSingleton() {
    return true;
  }
           

首先SqlSessionFactory是一個單例bean,在getObject()方法擷取SqlSessionFactory執行個體時,調用了afterPropertiesSet()方法:

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

afterPropertiesSet()方法為InitializingBean接口中的方法,是在spring中bean初始化後,init-method方法執行前調用。關于sping中bean的生命周期和bean的執行個體化和初始化的概念,請參考下邊連結:

https://blog.csdn.net/chuixue24/article/details/103696096

https://blog.csdn.net/chuixue24/article/details/103833475

下邊,繼續解析源碼,在afterPropertiesSet()中調用了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);
      }
    } 
    //一般我們是在xml檔案中配置configLocation,指明mybatis配置檔案路勁
    else if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      //讀取配置,并給configuration指派
      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);
      }
    }

    //解析是否配置了自定義objectFactory。
    //在MyBatis中,當其sq 映射配置檔案中的sql語句所得到的查詢結果,
    //被動态映射到resultType或其他處理結果集的參數配置對應的Java類型,其中就
    //JavaBean等封裝類。而objectFactory對象工廠就是用來建立實體對象的類。
    if (this.objectFactory != null) {
      configuration.setObjectFactory(this.objectFactory);
    }

    //對基本類型,基本對象,map和集合的包裝,除非你有比較特殊的對象需要自己實作,
    //才有用到這個配置。是以這個類的作用是擷取或設定bean,map或者list屬性的值。
    if (this.objectWrapperFactory != null) {
      configuration.setObjectWrapperFactory(this.objectWrapperFactory);
    }


    //解析虛拟檔案系統資源
    if (this.vfs != null) {
      configuration.setVfsImpl(this.vfs);
    }
    
    //根據包名解析類型别名(比如在mapper檔案中配置resultType和parameterType想使用實體類映射    
    //時,不用寫全限定類名)
    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");
        }
      }
    }
    //根據類全路徑解析類型别名(比如在mapper檔案中配置resultType和parameterType想使用實體類映    
    //射時,不用寫全限定類名)
    if (!isEmpty(this.typeAliases)) {
      for (Class<?> typeAlias : this.typeAliases) {
        //注冊類型别名
        configuration.getTypeAliasRegistry().registerAlias(typeAlias);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered type alias: '" + typeAlias + "'");
        }
      }
    }
    
    //解析mybatis配置檔案中的插件,比如分頁插件
    if (!isEmpty(this.plugins)) {
      for (Interceptor plugin : this.plugins) {
        configuration.addInterceptor(plugin);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered plugin: '" + plugin + "'");
        }
      }
    }

    //根據包名,解析類型處理器,在資料庫查詢出結果後,應該轉換成Java中的什麼類型,由它來決定
    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");
        }
      }
    }

    //根據類名,解析類型處理器,在資料庫查詢出結果後,應該轉換成Java中的什麼類型,由它來決定
    if (!isEmpty(this.typeHandlers)) {
      for (TypeHandler<?> typeHandler : this.typeHandlers) {
        configuration.getTypeHandlerRegistry().register(typeHandler);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered type handler: '" + typeHandler + "'");
        }
      }
    }
    
    //多資料庫環境支援,比如既有mysql,也有oracle
    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);
      }
    }

    //擴充Mybatis Cache接口,自定義緩存。
    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();
    }

    //生成把transactionFactory,dataSource包裝成環境資訊,并設定到configuration中
    //在通過SqlSessionFactory擷取session時,會通過configuration來擷取transactionFactory       
    //和資料源
    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
    
    //解析mapper檔案
    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");
      }
    }
    
    //利用configuration構造SqlSessionFactory執行個體
    return this.sqlSessionFactoryBuilder.build(configuration);
  }
           

上邊代碼核心流程都加了注釋,大部分都好了解,其中有幾個不太常見的配置:

objectWrapperFactory:

官方文檔的解釋:MyBatis 每次建立結果對象的新執行個體時,它都會使用一個對象工廠(ObjectFactory)執行個體來完成。 預設的對象工廠需要做的僅僅是執行個體化目标類,要麼通過預設構造方法,要麼在參數映射存在的時候通過參數構造方法來執行個體化。 如果想覆寫對象工廠的預設行為,則可以通過建立自己的對象工廠來實作。注意的是mybatis為我們實作了一個預設實作,那就是DefaultObjectFactory,這個已經足夠我們使用了。

objectWrapperFactory:mybatis對基本類型,基本對象,map和集合的包裝,除非你有比較特殊的對象需要自己實作,才有用到這個配置。是以這個類的作用是擷取或設定bean,map或者list屬性的值。

databaseIdProvider:多資料庫環境支援。要應用時,可以參考以下例子:

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>
		<!-- 使用jdbc的getGeneratedKeys擷取資料庫自增主鍵值 -->
		<setting name="useGeneratedKeys" value="true" />

		<!-- 使用列别名替換列名 預設:true -->
		<setting name="useColumnLabel" value="true" />

		<!-- 開啟駝峰命名轉換:Table{create_time} -> Entity{createTime} -->
		<setting name="mapUnderscoreToCamelCase" value="true" />
		
		<!-- 指定log4j2為日志輸出,開啟後,日志将記錄sql語句 -->
		<setting name="logImpl" value="LOG4J2" />  
	</settings>
    <databaseIdProvider type="DB_VENDOR">
        <property name="MySQL" value="mysql" />
        <property name="Oracle" value="oracle" />
    </databaseIdProvider>     
	<plugins>
        <plugin interceptor="com.chuixue.test.PrepareInterceptor"/>
	</plugins>
</configuration>
           

 mapper.xml配置檔案:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.elements.user.dao.dbMapper" >


  <select id="SelectTime"   resultType="String" databaseId="mysql">
   SELECT  NOW() FROM dual 
  </select>

  <select id="SelectTime"   resultType="String" databaseId="oracle">
   SELECT  'oralce'||to_char(sysdate,'yyyy-mm-dd hh24:mi:ss')  FROM dual 
  </select>


</mapper>
           

繼續源碼解析,在buildSqlSessionFactory()方法最後一行,調用sqlSessionFactoryBuilder構造SqlSessionFactory,傳回DefaultSqlSessionFactory的一個執行個體。

public class SqlSessionFactoryBuilder {

  //省略其他代碼……

    
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

}
           

DefaultSqlSessionFactory是SqlSessionFactory的預設實作,我們分别檢視下這兩個類的源碼,SqlSessionFactory源碼:

package org.apache.ibatis.session;

import java.sql.Connection;

public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);
  SqlSession openSession(Connection connection);
  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();

}
           

 可見SqlSessionFactory的主要功能是擷取SqlSession ,另外還有一個接口可以擷取Configuration 。再看下DefaultSqlSessionFactory的源碼:

public class DefaultSqlSessionFactory implements SqlSessionFactory {

  private final Configuration configuration;

  public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
  }

  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

  @Override
  public SqlSession openSession(ExecutorType execType, Connection connection) {
    return openSessionFromConnection(execType, connection);
  }

  @Override
  public Configuration getConfiguration() {
    return configuration;
  }

  //省略其他代碼……
}
           

在DefaultSqlSessionFactory中有一個Configuration的引用,好多方法都是通過Configuration的引用參與實作,可見Configuration的重要性。

在下一遍中将解析Configuration源碼。

參考連結:

https://www.jianshu.com/p/5069476595ab

繼續閱讀