mybatis源碼分析6 - 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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

我們只需要指定兩個屬性即可,一是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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上面是典型的單例模式,我們到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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163

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

  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);
}
  • 1
  • 2
  • 3

這個方法十分簡單,構造sqlSessionFactory的默認實現類DefaultSqlSessionFactory,並傳入前面創建並解析好的configuration對象即可。configuration包含了幾乎所有的mybatis配置信息,十分重要。

3 總結

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章