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