上一篇文章分析了 MyBatis 解析配置檔案初始化的流程,本篇主要分析一下SqlSession 具體的執行流程。
MyBatis 在解析完配置檔案後生成了一個
DefaultSqlSessionFactory
對象,後續執行 SQL 請求的時候都是調用其
openSession
方法獲得
SqlSessison
,相當于一個 SQL 會話。
SqlSession
提供了操作資料庫的一些方法,如
select
、
update
等。
openSession
先看一下
DefaultSqlSessionFactory
的
openSession
的代碼:
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 從 configuration 取出配置
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 每個 SqlSession 都有一個單獨的 Executor 對象
final Executor executor = configuration.newExecutor(tx, execType, autoCommit);
// 傳回 DefaultSqlSession 對象
return new DefaultSqlSession(configuration, executor);
} 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();
}
}
主要代碼在
openSessionFromDataSource
,首先是從
Configuration
中取出相關的配置,生成
Transaction
,接着又建立了一個
Executor
,最後傳回了
DefaultSqlSession
對象。
這裡的
Executor
是什麼呢?它其實是一個執行器,
SqlSession
的操作會交給
Executor
去執行。MyBatis 的
Executor
常用的有以下幾種:
- SimpleExecutor: 預設的 Executor,每個 SQL 執行時都會建立新的 Statement
- ResuseExecutor: 相同的 SQL 會複用 Statement
- BatchExecutor: 用于批處理的 Executor
- CachingExecutor: 可緩存資料的 Executor,用代理模式包裝了其它類型的 Executor
了解了
Executor
的類型後,看一下
configuration.newExecutor(tx, execType, autoCommit)
的代碼:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// 預設是 SimpleExecutor
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;
}
MyBatis 預設啟用一級緩存,即同一個
SqlSession
會共用同一個緩存,上面代碼最終傳回的是
CachingExecutor
。
getMapper
我們看一下SqlSession的getMapper方法實作,一路點過去,可以看到SqlSession -> DefaultSqlSession->Configuration->MapperRegistry->MapperProxyFactory->MapperProxy
public class Configuration {
...
public <T> void addMapper(Class<T> type) {
this.mapperRegistry.addMapper(type);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
}
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
public MapperRegistry(Configuration config) {
this.config = config;
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
public <T> boolean hasMapper(Class<T> type) {
return this.knownMappers.containsKey(type);
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
this.knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
}
在建立了
SqlSession
之後,下一步是生成 Mapper 接口的代理類,代碼如下:
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
可以看出是從
configuration
中取得
Mapper
,最終調用了
MapperProxyFactory
的
newInstance
。
MapperProxyFactory
在上一篇文章已經分析過,它是為了給
Mapper
接口生成代理類,其中關鍵的攔截邏輯在
MapperProxy
中,下面是其
invoke
方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 過濾一些不需要被代理的方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 從緩存中擷取 MapperMethod 然後調用
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
MapperProxy
中調用了
MapperMethod
的
execute
,下面是部分代碼:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
...
}
可以看出,最終調用了
SqlSession
的對應方法,也就是
DefaultSqlSession
中的方法。
select
先看一下
DefaultSqlSession
中
select
的代碼:
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
select
中調用了
executor
的
query
,上面提到,預設的
Executor
是
CachingExecutor
,看其中的代碼:
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 擷取緩存的key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 擷取緩存
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 調用代理對象的緩存
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
首先檢查緩存中是否有資料,如果沒有再調用代理對象的
query
,預設是
SimpleExecutor
。
Executor
是一個接口,下面有個實作類是
BaseExecutor
,其中實作了其它
Executor
通用的一些邏輯,包括
doQuery
以及
doUpdate
等,其中封裝了 JDBC 的相關操作。
update
update
的執行與
select
類似, 都是從
CachingExecutor
開始,看代碼:
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
// 檢查是否需要重新整理緩存
flushCacheIfRequired(ms);
// 調用代理類的 update
return delegate.update(ms, parameterObject);
}
update
會使得緩存的失效,是以第一步是檢查是否需要重新整理緩存,接下來再交給代理類去執行真正的資料庫更新操作。
SqlSession下的四大對象
通過上面的分析,映射器就是一個動态代理對象,進入到了MapperMethod的execute方法,它經過簡單的判斷就進入了SqlSession的删除、更新、插入、選擇等方法,這些方法如何執行是下面要介紹的内容。
Mapper執行的過程是通過Executor、StatementHandler、ParameterHandler和ResultHandler來完成資料庫操作和結果傳回的,了解他們是編寫插件的關鍵:
- Executor:執行器,由它統一排程其他三個對象來執行對應的SQL;
- StatementHandler:使用資料庫的Statement執行操作;
- ParameterHandler:用于SQL對參數的處理;
- ResultHandler:進行最後資料集的封裝傳回處理;
在MyBatis中存在三種執行器:
- SIMPLE:簡易執行器,預設的執行器;
- REUSE:執行重用預處理語句;
- BATCH:執行重用語句和批量更新,針對批量專用的執行器;
以SimpleExecutor為例,說明執行過程
public class SimpleExecutor extends BaseExecutor {
/**
* 執行查詢操作
*/
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 handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
/**
* 初始化StatementHandler
*/
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection);
handler.parameterize(stmt);
return stmt;
}
/**
* 執行查詢
*/
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.<E>handleResultSets(statement);
}
}
可以看到最後會委托給StatementHandler會話器進行處理,它是一個接口,實際建立的是RoutingStatementHandler對象,但它不是真實的服務對象,它是通過擴充卡模式找到對應的StatementHandler執行的。在MyBatis中,StatementHandler和Executor一樣分為三種:SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler。
Executor會先調用StatementHandler的prepare方法預編譯SQL語句,同時設定一些基本運作的參數。然後調用parameterize()方法啟用ParameterHandler設定參數,完成預編譯,跟着執行查詢,用ResultHandler封裝結果傳回給調用者。
總結
本文主要分析了
SqlSession
的執行流程,結合上一篇文章基本了解了 MyBatis 的運作原理。