1. 程式人生 > >mybatis-spring容器初始化

mybatis-spring容器初始化

1 引言

使用 MyBatis-Spring 模組,我們可以在Spring中使用mybatis,讓Spring容器來管理sqlSessionFactory單例的建立。如以下程式碼

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <!--指定資料來源,不用再在mybatis的XML配置檔案中指定environment了-->
  <property name="dataSource" ref="dataSource" />
  <!--指定configuration物件,它是建立sqlSessionFactory的核心,包含mybatis幾乎全部的配置資訊-->
<property name="configuration"> <bean class="org.apache.ibatis.session.Configuration"> <property name="mapUnderscoreToCamelCase" value="true"/> </bean> </property> <!--資料庫對映mapper檔案的位置--> <property name="mapperLocations" value="classpath*:com/xxt/ibatis/dbcp/**/*.xml"
/>
<!--或指定指定sqlMapConfig總配置檔案位置configLocation,建議採用這種mybatis配置單獨放在另一個XML中的方式--> <property name="configLocation" value="classpath:sqlMapConfig.xml"/> </bean>

我們只需要指定兩個屬性即可,一是dataSource資料庫源,二是configuration物件或configLocation配置檔案所在位置。那麼有這兩個屬性是如何建立sqlSessionFactory物件的呢,這一節我們詳細分析。

2 sqlSessionFactory物件注入的流程

建立sqlSessionFactory bean時,指定的實現類是SqlSessionFactoryBean類,它是一個FactoryBean。我們知道,對於FactoryBean,Spring為我們建立的不是FactoryBean本身的物件,二是它的getObject()方法返回的物件。故我們從SqlSessionFactoryBean的getObject()方法來分析。

// 工廠bean,它返回的不是FactoryBean本身,而是它的getObject方法返回的bean
public SqlSessionFactory getObject() throws Exception {
  if (this.sqlSessionFactory == null) {
    afterPropertiesSet();
  }

  // getObject最終返回的還是一個SqlSessionFactory物件
  return this.sqlSessionFactory;
}

上面是典型的單例模式,我們到afterPropertiesSet()方法中去看。

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

  // 建立sqlSessionFactory
  this.sqlSessionFactory = buildSqlSessionFactory();
}

afterPropertiesSet先做dataSource等屬性值的校驗,注入sqlSessionFactory的時候,必須傳入dataSource屬性的。然後呼叫buildSqlSessionFactory()方法來建立sqlSessionFactory,它是一個關鍵方法,我們詳細分析。

// 建立SqlSessionFactory例項
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
  // 包含了幾乎所有mybatis配置資訊,建立sqlSessionFactory最重要的變數,之前分析mybatis初始化的時候講到過
  Configuration configuration;

  // 先讀取sqlSessionFactory bean注入時,用來設定mybatis配置資訊Configuration的屬性
  // 有configuration屬性或者configLocation屬性兩種。
  XMLConfigBuilder xmlConfigBuilder = null;

  if (this.configuration != null) {
    // 注入的是configuration屬性時,它是一個bean
    configuration = this.configuration;
    // 合併configurationProperties變數到configuration的variables成員中。mybatis初始化的章節講到過這個合併
    // configurationProperties包含的是一些動態化常量,比如資料庫的username和password等資訊
    // configurationProperties屬性同樣在sqlSessionFactory bean注入時設定進來
    if (configuration.getVariables() == null) {
      configuration.setVariables(this.configurationProperties);
    } else if (this.configurationProperties != null) {
      configuration.getVariables().putAll(this.configurationProperties);
    }

  } else if (this.configLocation != null) {
    // 注入的是configLocation屬性時,它是一個String,描述了mybatis xml配置檔案的位置
    // 此時使用mybatis的配置檔案來配置其他屬性,利用配置檔案生成Configuration物件
    // 和原生mybatis一樣,也是先建立XMLConfigBuilder物件,然後利用它來解析mybatis配置檔案,然後將配置檔案中的屬性設定到configuration的相關成員變數中去
    // 此處只是建立XMLConfigBuilder和configuration物件,還沒有做解析
    xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
    configuration = xmlConfigBuilder.getConfiguration();

  } else {
    // configuration屬性和configLocation屬性都沒有注入時,只能直接構造mybatis預設的Configuration物件了
    LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
    configuration = new Configuration();
    // 同樣合併configurationProperties屬性到configuration變數的variables變數中
    if (this.configurationProperties != null) {
      configuration.setVariables(this.configurationProperties);
    }
  }

  // 注入了objectFactory屬性時,一般不建議在sqlSessionFactory中注入,而是放到mybatis配置檔案中。
  if (this.objectFactory != null) {
    configuration.setObjectFactory(this.objectFactory);
  }

  // 注入了objectWrapperFactory屬性時,一般不建議在sqlSessionFactory中注入,而是放到mybatis配置檔案中。
  if (this.objectWrapperFactory != null) {
    configuration.setObjectWrapperFactory(this.objectWrapperFactory);
  }

  // 注入了vfs屬性時,一般不建議在sqlSessionFactory中注入,而是放到mybatis配置檔案中。
  if (this.vfs != null) {
    configuration.setVfsImpl(this.vfs);
  }

  // 注入了typeAliasesPackage屬性時,一般不建議在sqlSessionFactory中注入,而是放到mybatis配置檔案中。
  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);
      LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for aliases");
    }
  }

  // 注入了typeAliases屬性時,一般不建議在sqlSessionFactory中注入,而是放到mybatis配置檔案中。
  if (!isEmpty(this.typeAliases)) {
    for (Class<?> typeAlias : this.typeAliases) {
      configuration.getTypeAliasRegistry().registerAlias(typeAlias);
      LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
    }
  }

  // 注入了plugins屬性時,一般不建議在sqlSessionFactory中注入,而是放到mybatis配置檔案中。
  if (!isEmpty(this.plugins)) {
    for (Interceptor plugin : this.plugins) {
      configuration.addInterceptor(plugin);
      LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
    }
  }

  // 注入了typeHandlersPackage屬性時,一般不建議在sqlSessionFactory中注入,而是放到mybatis配置檔案中。
  if (hasLength(this.typeHandlersPackage)) {
    String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
        ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    for (String packageToScan : typeHandlersPackageArray) {
      configuration.getTypeHandlerRegistry().register(packageToScan);
      LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for type handlers");
    }
  }

  // 注入了typeHandlers屬性時,一般不建議在sqlSessionFactory中注入,而是放到mybatis配置檔案中。
  if (!isEmpty(this.typeHandlers)) {
    for (TypeHandler<?> typeHandler : this.typeHandlers) {
      configuration.getTypeHandlerRegistry().register(typeHandler);
      LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
    }
  }

  // 注入了databaseIdProvider屬性時,一般不建議在sqlSessionFactory中注入,而是放到mybatis配置檔案中。
  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);
    }
  }

  // 注入了cache屬性時,新增到configuration變數的cache map中
  if (this.cache != null) {
    configuration.addCache(this.cache);
  }

  // 使用configLocation屬性時,解析mybatis xml配置檔案,和直接使用原生mybatis的new SqlSessionFactoryBuild().build()方式幾乎相同
  if (xmlConfigBuilder != null) {
    try {
      // 利用前面建立的xmlConfigBuilder來解析XML配置檔案,並將解析後的鍵值對設定到configuration變數中
      xmlConfigBuilder.parse();
      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();
    }
  }

  // 建立transactionFactory,用來建立transaction事務,Spring使用AOP來建立事務
  if (this.transactionFactory == null) {
    this.transactionFactory = new SpringManagedTransactionFactory();
  }

  // 設定configuration的environment變數,
  // 採用Spring注入方式時,直接指定了sqlSessionFactory下的dataSource資料庫源,一般不需要在mybaits配置檔案中設定environments了
  configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

  // 注入了mapperLocations屬性時,一般不建議在sqlSessionFactory中注入,而是放到mybatis配置檔案中。
  if (!isEmpty(this.mapperLocations)) {
    for (Resource mapperLocation : this.mapperLocations) {
      if (mapperLocation == null) {
        continue;
      }

      try {
        // 讀取mapper配置檔案,並解析
        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();
      }
      LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
    }
  } else {
    LOGGER.debug(() -> "Property 'mapperLocations' was not specified or no matching resources found");
  }

  // configuration變數建立並初始化好之後,就可以建立sqlSessionFactory物件了
  // sqlSessionFactoryBuilder的build建立DefaultSqlSessionFactory物件,預設的SqlSessionFactory
  // 這個過程之前講解mybatis初始化的章節時,講過了的
  return this.sqlSessionFactoryBuilder.build(configuration);
}

這個方法比較長,詳細內容讀者可以逐行看上面程式碼和註釋,註釋應該已經十分詳盡了。我們總結下這個方法的流程。

  1. 先讀取mybatis配置資訊,它通過sqlSessionFactory注入時,傳入的configuration物件或者configLocation String來分析配置資訊。

    1)傳入的是configuration屬性時,合併configurationProperties屬性到configuration物件中去即可。

    2)傳入的是configLocation屬性時,它是一個String,描述了mybatis xml配置檔案的位置。先建立XMLConfigBuilder物件和configuration物件,後面幾步會解析mybatis配置檔案,然後將配置檔案中的屬性設定到configuration的相關成員變數中去(這個過程和原生mybatis相同)

    3)configuration屬性和configLocation屬性都沒有注入時,只能直接構造mybatis預設的Configuration物件了

  2. 再讀取建立sqlSessionFactory bean時,傳入的其他屬性,如objectFactory objectWrapperFactory vfs typeAliasesPackage typeAliases plugins typeHandlersPackage typeHandlers databaseIdProvider等。如果我們使用配置檔案位置資訊configLocation來解析mybatis配置資訊的話,這些屬性均不需要傳入。如果採用configuration物件的方式,或者configLocation和configuration都沒有傳入的話,則需要這些屬性了。一般建議採用configLocation的方式,將mybatis的配置資訊和Spring配置資訊相分離。

  3. 使用configLocation屬性時,解析mybatis xml配置檔案,和直接使用原生mybatis的new SqlSessionFactoryBuild().build()方式幾乎相同。

  4. 建立transactionFactory,用來建立transaction事務,Spring使用AOP來建立事務

  5. 設定configuration的environment變數,利用傳入的dataSource屬性

  6. 讀取建立sqlSessionFactory bean時,傳入的mapperLocations屬性。如果採用configLocation指定mybatis配置檔案位置的方式,則一般不需要在Spring中配置mapperLocations

  7. sqlSessionFactoryBuilder的build建立DefaultSqlSessionFactory物件

這個方法很關鍵,且流程很長。大家最重要的是要知道,建立sqlSessionFactory時指定mybatis配置資訊,有三種方式。一是直接configuration物件,包含了配置資訊各項引數。二是configLocation字串,指定了配置檔案的位置。三是configuration和configLocation均沒有配置,完全依靠Spring配置檔案中指定objectFactory typeHandlers 等屬性。明白了這一點,上面的程式碼就會比較清晰了。

為了將Spring配置資訊和mybatis配置資訊相分離,從而讓各個XML各司其職,也避免Spring配置檔案過於膨脹,我們一般採用configLocation的方式。這種方式和原生mybatis建立sqlSessionFactory的過程極其類似,都是通過XMLConfigBuilder解析XML配置檔案,並將解析到的鍵值對設定到Configuration物件的相關變數中去。這一過程我們在前面講解mybatis初始化的章節中已經詳細介紹了,故此處不詳細講解了。最後我們看sqlSessionFactoryBuilder.build()方法。

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

這個方法十分簡單,構造sqlSessionFactory的預設實現類DefaultSqlSessionFactory,並傳入前面建立並解析好的configuration物件即可。configuration包含了幾乎所有的mybatis配置資訊,十分重要。

3 總結

Spring容器中sqlSessionFactory的建立其實是十分簡單的,特別是採用了configLocation方式的時候。建立過程基本是依賴原生mybatis的執行流程的。從這兒也可以看出程式碼分層有利於程式碼適配。這也是我們平時自己設計框架時要要注意的地方,儘量讓層次分明,模組解耦,這樣才能簡易的適配不同的環境,從而提高可移植性。

下一節我們分析mybatis-spring中,sqlSession是如何操作資料庫的

相關文章