Mybatis啟動流程詳解
今天,我擬從一個簡單的selectOne查詢入手,追蹤mybatis框架執行的足跡。
Mybatis整體流程圖
單元測試程式碼(selectOne型別)
@Test
public void queryFinancialAccountTest(){
FundFinancialExtDTO financialAccountExtPO = new FundFinancialExtDTO();
// 部分程式碼略去
FundAccAndExtDTO fundAccAndExtDTO = fundFinancialExtMapper.queryFinancialAccount(financialAccountExtPO);
}
mybatis.xml配置
<!-- Mybatis配置 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.roger.practice.dal.dao" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" >
<property name="dataSource" ref="dataSource" />
<property name="typeAliasesPackage" value="com.roger.practice.entity" />
<property name="mapperLocations" value="classpath*:mapper/**/*.xml" />
<property name="plugins">
<array>
<bean class="com.roger.practice.mybatis.page.interceptor.PageInterceptor" />
<bean class="com.roger.practice.mybatis.page.interceptor.PageSqlRewriteInterceptor">
<property name="dialect" value="oracle" />
</bean>
</array>
</property>
</bean>
初始化部分 SqlSessionFactoryBuilder
Mybatis整合在Spring中,方法afterPropertiesSet()將在所有的屬性被初始化後被呼叫。檢視org.mybatis.spring.SqlSessionFactoryBean類中的afterPropertiesSet()方法,發現該方法開始建立SqlSessionFactory例項。
@Override
public void afterPropertiesSet() throws Exception {
// 略去程式碼請參考原始碼
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
// 略去程式碼將物件工廠objectFactory,物件包裝工廠objectWrapperFactory,類型別名typeAliasesPackage/typeAliases,外掛plugins,型別處理器typeHandlersPackage/typeHandlers,快取cache,環境environments,事務工廠transactionFactory等資訊配置存到Configuration物件中。
// 通過xmlMapperBuilder來解析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");
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
org.apache.ibatis.session.SqlSessionFactoryBuilder提供了9種構造SqlSessionFactory的方法,但最終都要呼叫包含Configuration物件的構造方法,其通過載入配置檔案構造SqlSessionFactory物件、返回DefaultSqlSessionFactory物件。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
org.apache.ibatis.binding.MapperProxyFactory
Spring負責建立SqlSessionTemplate,執行getMapper方法時會建立動態代理,代理Test用例中的FundFinancialExtMapper介面。
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
SelectOne查詢流程
org.apache.ibatis.binding.MapperProxy
Mybatis初始化載入的時候,利用MapperProxy代理了自己的Mapper介面類,生成一個代理處理類。代理處理的邏輯都在invoke方法裡,它根據目標類的介面(本例是FundFinancialExtMapper)生成 MapperMethod。sqlSession是由spring負責生成的SqlSessionTemplate,它是spring連線mybatis的模板類。接下來呼叫MapperMethod的execute方法就能獲取執行結果。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
org.apache.ibatis.binding.MapperMethod
MapperMethod就像是一個分發者,它根據SqlCommandType,並獲取執行引數commandName和param,交由SqlSessionTemplate物件執行具體的操作。這樣mapper物件與sqlSession就真正的關聯起來了。本例中,將執行SqlSessionTemplate類中的selectOne方法。
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
org.mybatis.spring.SqlSessionTemplate
SqlSessionTemplate的實際執行是交給它的代理類完成的。檢視SqlSessionTemplate建構函式可知,它是由內部類SqlSessionInterceptor動態代理的,所有的處理邏輯都是在invoke方法裡。invoke方法裡執行了:
1. 通過靜態方法SqlSessionUtils.getSqlSession建立sqlSession,實際返回DefaultSqlSession物件。
2. DefaultSqlSession執行selectOne方法。
3. 執行成功則提交,出現異常則關閉sqlSession。
// SqlSessionTemplate的建構函式
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
// 內部類SqlSessionInterceptor
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
// 略去程式碼請參考原始碼
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
org.apache.ibatis.executor.Executor
sqlSession只是一個門面,真正發揮作用的是executor,對sqlSession方法的訪問最終都會落到executor的相應方法上去。
executor物件是執行openSessionFromDataSource方法時建立的,見org.apache.ibatis.session.Configuration裡的newExecutor方法。executor具體實現是SimpleExecutor,由於cacheEnabled預設為ture,還追加了快取功能。另外它還可以追加攔截器。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
// 實現快取
executor = new CachingExecutor(executor);
}
// 追加攔截器,比如分頁攔截器
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
org.apache.ibatis.executor.CachingExecutor
query方法會最終委派org.apache.ibatis.executor.SimpleExecutor類中的doQuery方法。query方法如下:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
org.apache.ibatis.executor.SimpleExecutor
SimpleExecutor的doQuery方法是具體的實現。
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// StatementHandler的建立與executor的實現很相似,又是在Configuration裡通過newStatementHandler方法建立的。
// 由org.apache.ibatis.executor.statement.RoutingStatementHandler代理實際的StatementHandler實現。
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 對查詢語句進行預編譯,並解析引數實體。
stmt = prepareStatement(handler, ms.getStatementLog());
// 方法執行時改由PreparedStatementHandler實際代理進行查詢
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
通過StatementType獲取具體的StatementHandler類,從預設配置可知,PreparedStatementHandler是實際代理的物件。其中構造StatementHandler的時候,檢視BaseStatementHandler建構函式可知,ParameterHandler和ResultSetHandler也是有Configuration生成的,同樣也可追加攔截器。
org.apache.ibatis.executor.statement.PreparedStatementHandler
PreparedStatementHandler的父類是BaseStatementHandler,BaseStatementHandler的建構函式是有這麼一段:
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
它觸發了sql 的解析,在解析sql的過程中,TypeHandler也被決斷出來了,決斷的原則就是根據引數的型別和引數對應的JDBC型別決定使用哪個TypeHandler。比如:引數型別是String的話就用StringTypeHandler,引數型別是整數的話就用IntegerTypeHandler等。
query方法如下,它執行了execute方法並完成結果集的對映。
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 實際執行在此處那!!!
ps.execute();
// 由org.apache.ibatis.executor.resultset.DefaultResultSetHandler代理實現
// 完成結果集的對映
return resultSetHandler.<E> handleResultSets(ps);
}
org.apache.ibatis.scripting.defaults.DefaultParameterHandler
setParameters方法用來解析引數實體,其中propertyName獲取引數名,value是引數值,typeHandler和jdbcType是引數型別。
org.apache.ibatis.executor.resultset.DefaultResultSetHandler
完成結果集的對映。
org.apache.ibatis.session.SqlSessionFactory
SqlSessionFactory作為SqlSession的工廠,提供了8種獲取SqlSession的方法,同時還提供了獲取Configuration的方法。
8種獲取SqlSession的方法主要涉及4個引數:是否自動提交、自定義Connection、事務級別、ExecutorType(Statement型別【普通、預處理、批處理】)。包含Connection型別引數的方法會呼叫openSessionFromConnection方法,其它都會呼叫openSessionFromDataSource方法,最終都返回DefaultSqlSession物件。
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory是SqlSessionFactory介面的實現,以openSessionFromDataSource方法為例,建立sqlsession經過了以下幾個主要步驟:
1) 從配置中獲取Environment;
2) 根據Environment建立事務工廠TransactionFactory;
3) 從Environment中取得DataSource、進而建立事務物件Transaction;
4) 建立Executor物件;
5) 建立sqlsession物件。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
autoCommit = true;
}
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = transactionFactory.newTransaction(connection);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
org.apache.ibatis.session.SqlSession
org.apache.ibatis.session.defaults.DefaultSqlSession
DefaultSqlSession實現了SqlSession介面,主要封裝了Configuration物件、Executor物件、是否自動提交。
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
public DefaultSqlSession(Configuration configuration, Executor executor) {
// 預設不自動提交
this(configuration, executor, false);
}
它利用自己封裝的一套東西,還包括Executor(封裝Statement)、ResultHandler(封裝處理ResultSet物件)、RowBounds(封裝分頁物件),提供了CRUD、提供了快取機制、提供了根據配置檔案獲取Sql語句的方法,提供了事務的提交和回滾等。
總結
大體流程就是:
1. 載入XML配置檔案建立Configuration物件完成初始化,建立並使用SqlSessionFactory物件。
2. 利用MapperProxy代理具體的Mapper介面類,生成了MapperMethod。
3. Spring負責生成SqlSessionTemplate,它實際由SqlSessionInterceptor動態代理,所有的處理邏輯都是在 invoke方法裡,主要是獲取SqlSession、生成可帶快取可追加外掛的Executor,並執行操作。DefaultSqlSession由SimpleExecutor靜態代理執行查詢操作。
4. RoutingStatementHandler根據配置Statement型別建立真正執行資料庫操作的StatementHandler,實際由PreparedStatementHandler進行查詢語句的預編譯、查詢引數實體解析、執行查詢。
5. DefaultResultSetHandler完成結果集的對映。