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
這個方法比較長,詳細內容讀者可以逐行看上面代碼和註釋,註釋應該已經十分詳盡了。我們總結下這個方法的流程。
先讀取mybatis配置信息,它通過sqlSessionFactory注入時,傳入的configuration對象或者configLocation String來分析配置信息。
1)傳入的是configuration屬性時,合併configurationProperties屬性到configuration對象中去即可。
2)傳入的是configLocation屬性時,它是一個String,描述了mybatis xml配置文件的位置。先創建XMLConfigBuilder對象和configuration對象,後面幾步會解析mybatis配置文件,然後將配置文件中的屬性設置到configuration的相關成員變量中去(這個過程和原生mybatis相同)
3)configuration屬性和configLocation屬性都沒有注入時,只能直接構造mybatis默認的Configuration對象了
再讀取創建sqlSessionFactory bean時,傳入的其他屬性,如objectFactory objectWrapperFactory vfs typeAliasesPackage typeAliases plugins typeHandlersPackage typeHandlers databaseIdProvider等。如果我們使用配置文件位置信息configLocation來解析mybatis配置信息的話,這些屬性均不需要傳入。如果採用configuration對象的方式,或者configLocation和configuration都沒有傳入的話,則需要這些屬性了。一般建議採用configLocation的方式,將mybatis的配置信息和Spring配置信息相分離。
使用configLocation屬性時,解析mybatis xml配置文件,和直接使用原生mybatis的new SqlSessionFactoryBuild().build()方式幾乎相同。
創建transactionFactory,用來創建transaction事務,Spring使用AOP來創建事務
設置configuration的environment變量,利用傳入的dataSource屬性
讀取創建sqlSessionFactory bean時,傳入的mapperLocations屬性。如果採用configLocation指定mybatis配置文件位置的方式,則一般不需要在Spring中配置mapperLocations
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是如何操作數據庫的