前言
在了解了MyBatis初始化加載程序后,我們也應該研究看看SQL執行程序是怎樣執行?這樣我們對于Mybatis的整個執行流程都熟悉了,在開發遇到問題也可以很快定位到問題,
更重要的,在面試中遇到面試官咨詢Mybatis的知識點的時候,可以很順暢的把這一套流程講出來,面試官也會覺得你已掌握Mybatis知識點了,可能就不問了,趕緊瞄瞄,
簡介SQL執行程序
經過MyBatis初始化加載Sql執行程序所需的資訊后,我們就可以通過 SqlSessionFactory 物件得到 SqlSession ,然后執行 SQL 陳述句了,接下來看看Sql執行具體程序,SQL大致執行流程圖如下所示:

接下來我們來看看每個執行鏈路中的具體執行程序,
SqlSession
SqlSession 是 MyBatis 暴露給外部使用的統一介面層,通過 SqlSessionFactory 創建,且其是包含和資料庫打交道所有操作介面,
下面通過時序圖描述 SqlSession 物件的創建流程:

在生成SqlSession的同時,基于executorType初始化好Executor 實作類,
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;
}
最頂層的SqlSession介面已生成,那我們可以來看看sql的執行程序下一步是怎樣的呢?怎樣使用代理類MapperProxy,
MapperProxy
MapperProxy 是 Mapper介面與SQL 陳述句映射的關鍵,通過 MapperProxy 可以讓對應的 SQL 陳述句跟介面進行系結的,具體流程如下:
MapperProxy代理類生成流程MapperProxy代理類執行操作
MapperProxy代理類生成流程

其中,MapperRegistry 是 Configuration 的一個屬性,在決議配置時候會在MapperRegistry 中快取了 MapperProxyFactory 的 knownMappers 變數Map 集合,
``MapperRegistry會根據mapper介面型別獲取已快取的MapperProxyFactory,MapperProxyFactory會基于SqlSession來生成MapperProxy`代理物件,
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);
}
}
}
當呼叫SqlSession介面時,MapperProxy怎么是實作的呢?MyBatis 的 Mapper介面 是通過動態代理實作的,呼叫 Mapper 介面的任何方法都會執行 MapperProxy::invoke() 方法,

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//Object型別執行
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
//介面默認方法執行
if (method.isDefault()) {
if (privateLookupInMethod == null) {
return this.invokeDefaultMethodJava8(proxy, method, args);
}
return this.invokeDefaultMethodJava9(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
但最侄訓呼叫到mapperMethod::execute() 方法執行,主要是判斷是 INSERT、UPDATE、DELETE 、SELECT 陳述句去操作,其中如果是查詢的話,還會判斷回傳值的型別,
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
通過以上的分析,總結出
Mapper介面實際物件為代理物件MapperProxy;MapperProxy繼承InvocationHandler,實作invoke方法;MapperProxyFactory::newInstance()方法,基于 JDK 動態代理的方式創建了一個MapperProxy的代理類;- 最侄訓呼叫到
mapperMethod::execute()方法執行,完成操作, - 而且更重要一點是,
MyBatis使用的動態代理和普遍動態代理有點區別,沒有實作類,只有介面,MyBatis 動態代理類圖結構如下所示:

已以SELECT 為例, 呼叫會SqlSession ::selectOne() 方法,繼續往下執行,會執行 Executor::query() 方法,
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
} finally {
ErrorContext.instance().reset();
}
return var5;
}
執行到Executor類,那么我們來看看其究竟有什么?
Executor
Executor物件為SQL 的執行引擎,負責增刪改查的具體操作,頂層介面SqlSession中都會有一個 Executor 物件,可以理解為 JDBC 中 Statement 的封裝版,
Executor 是最頂層的是執行器,它有兩個實作類,分別是BaseExecutor和 CachingExecutor
-
BaseExecutor是一個抽象類,實作了大部分Executor介面定義的功能,降低了介面實作的難度,BaseExecutor基于配接器設計模式之介面適配會有三個子類,分別是SimpleExecutor、ReuseExecutor和BatchExecutor,-
SimpleExecutor: 是 MyBatis 中默認簡單執行器,每執行一次update或select,就開啟一個Statement物件,用完立刻關閉Statement物件 -
ReuseExecutor: 可重用執行器, 執行update或select,以sql作為key查找Statement物件,存在就使用,不存在就創建,用完后,不關閉Statement物件,而是放置于Map<String, Statement>內,供下一次使用,簡言之,就是重復使用Statement物件 -
BatchExecutor: 批處理執行器,用于執行update(沒有select,JDBC批處理不支持select將多個 SQL 一次性輸出到資料庫,
-
-
CachingExecutor: 快取執行器,為Executor物件增加了二級快取的相關功:先從快取中查詢結果,如果存在就回傳之前的結果;如果不存在,再委托給Executor delegate去資料庫中取,delegate可以是上面任何一個執行器,
在Mybatis組態檔中,可以指定默認的ExecutorType執行器型別,也可以手動給DefaultSqlSessionFactory的創建SqlSession的方法傳遞ExecutorType型別引數,
看完Exector簡介之后,繼續跟蹤執行流程鏈路分析,SqlSession 中的 JDBC 操作部分最終都會委派給 Exector 實作,Executor::query()方法,看看在Exector的執行是怎樣的?

每次查詢都會先經過CachingExecutor快取執行器, 會先判斷二級快取中是否存在查詢 SQL ,如果存在直接從二級快取中獲取,不存在即為第一次執行,會直接執行SQL 陳述句,并創建快取,都是由CachingExecutor::query()操作完成的,
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
return this.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();
//sql查詢是否存在在二級快取中
if (cache != null) {
//根據 <select> 節點的配置,判斷否需要清空二級快取
this.flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
this.ensureNoOutParams(ms, boundSql);
//查詢二級快取
List<E> list = (List)this.tcm.getObject(cache, key);
if (list == null) {
//二級快取沒用相應的結果物件,呼叫封裝的Executor物件的 query() 方法
list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//將查詢結果保存到二級快取中
this.tcm.putObject(cache, key, list);
}
return list;
}
}
//沒有啟動二級快取,直接呼叫底層 Executor 執行資料資料庫查詢操作
return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
如果在經過CachingExecutor快取執行器(二級快取)沒有回傳值的話,就會執行BaseExecutor 以及其的實作類,默認為SimpleExecutor ,首先會在一級快取中獲取查詢結果,獲得不到,最侄訓通過SimpleExecutor:: ()去資料庫中查詢,
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
//是否清除本地快取
if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
this.clearLocalCache();
}
List list;
try {
++this.queryStack;
//從一級快取中,獲取查詢結果
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
//獲取到結果,則進行處理
if (list != null) {
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//獲得不到,則從資料庫中查詢
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
--this.queryStack;
}
if (this.queryStack == 0) {
//執行延遲加載
Iterator var8 = this.deferredLoads.iterator();
while(var8.hasNext()) {
BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
deferredLoad.load();
}
this.deferredLoads.clear();
if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
this.clearLocalCache();
}
}
return list;
}
}
那么SimpleExecutor::doQuery()如何去資料庫中查詢獲取到結果呢?其實執行到這邊mybatis的執行程序就從 Executor轉交給 StatementHandler處理,
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
這樣我們的執行鏈路分析已到StatementHandler了,現在讓我們去一探究竟其原理
StatementHandler
StatementHandler負責處理Mybatis與JDBC之間Statement的互動,即Statement物件與資料庫進行互動,其為頂級介面,有4個實作類,其中三個是Statement物件與資料庫進行互動類, 另外一個是路由功能的,
RoutingStatementHandler: 對Statement物件沒有實際操作,主要負責另外三個StatementHandler的創建及呼叫, 而且在MyBatis執行時,使用的StatementHandler介面物件實際上就是RoutingStatementHandler物件,SimpleStatementHandler: 管理 Statement 物件, 用于簡單SQL的處理 ,PreparedStatementHandler: 管理 Statement 物件,預處理SQL的介面 ,CallableStatementHandler:管理 Statement 物件,用于執行存盤程序相關的介面 ,

在經歷過Executor后,基于初始化加載到MapperState中的StatementType的型別通過Configuration.newStatementHandler()方法中的RoutingStatementHandler 生成StatementHandler實際處理類,
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch(ms.getStatementType()) {
case STATEMENT:
this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
現在先以PreparedStatementHandler預處理為例,接著Sql的執行鏈路來分析,StatementHandler::query()到StatementHandler::execute()真正執行Sql查詢操作,
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.handleResultSets(statement);
}
但執行真正查詢操作之前,還進行哪些處理呢?還會進行ParameterHandler對 SQL 引數的預處理:對引數進行動態Sql映射,那么ParameterHandler又如何實作對引數進行動態映射的呢?
ParameterHandler
ParameterHandler 引數處理器, 用來設定引數規則的,負責為sql 陳述句引數動態賦值,其有兩個介面
- getParameterObject: 用于讀取引數
- setParameters: 用于對 PreparedStatement 的引數賦值
當SimpleExecutor執行構造PreparedStatementHandler完,會呼叫parameterize()方法將PreparedStatement物件里SQL轉交ParameterHandler實作類 DefaultParameterHandler::setParameters()方法 設定 PreparedStatement 的占位符引數 ,
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
//引數動態賦值
handler.parameterize(stmt);
return stmt;
}
DefaultParameterHandler::setParameters()如何對SQL進行動態賦值呢?在執行前將已裝載好的BoundSql物件資訊進行使用
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//獲取待動態賦值引數串列的封裝parameterMappings
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
//是否為輸入引數
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
//獲取待動態引數屬性名
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
在通過 SqlSource 的parse 方法得到parameterMappings 的具體實作中,我們會得到parameterMappings 的 typeHandler
TypeHandler typeHandler = parameterMapping.getTypeHandler();
//獲取jdbc資料型別
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
//
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
執行完SQL 引數的預處理,當StatementHandler::execute()真正執行查詢操作執行完后,有回傳結果,需要對回傳結果進行ResultSetHandler處理,現在看看最后的結果的處理流程,
ResultSetHandler
ResultSetHandler結果決議器,將查詢結果的ResultSet 轉換成映射的對應結果(java DTO等),其有三介面
handleResultSets():處理結果集handleCursorResultSets():批量處理結果集handleOutputParameters():處理存盤程序回傳的結果集
其默認的實作為DefaultResultSetHandler,主要功能為:
- 處理
Statement執行后產生的結果集生成相對的輸出結果、 - 處理存盤程序執行后的輸出引數
那看看DefaultResultSetHandler::handleResultSets()如何處理?
- 當有多個
ResultSet的結果集合,每個ResultSet對應一個Object 物件,如果不考慮存盤程序,普通的查詢只有一個ResultSet ResultSetWrapper封裝了ResultSet結果集,其屬性包含ResultSet,ResultMap等
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
//當有多個ResultSet的結果集合,每個ResultSet對應一個Object 物件
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
//獲得首個 ResultSet 物件,并封裝成 ResultSetWrapper 物件
ResultSetWrapper rsw = getFirstResultSet(stmt);
//獲得 ResultMap 陣列
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount); // <3.1> 校驗
while (rsw != null && resultMapCount > resultSetCount) {
//獲得 ResultMap 物件
ResultMap resultMap = resultMaps.get(resultSetCount);
//處理 ResultSet ,將結果添加到 multipleResults 中
handleResultSet(rsw, resultMap, multipleResults, null);
//獲得下一個 ResultSet 物件,并封裝成 ResultSetWrapper 物件
rsw = getNextResultSet(stmt);
//清理
cleanUpAfterHandlingResultSet();
// resultSetCount ++
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
//如果是 multipleResults 單元素,則取首元素回傳
return ollapseSingleResultList(multipleResults);
}
其實在ResultSetHandler結果集處理是比較復雜的,這里只是簡單的介紹一下,有興趣的可以再深入研究一下,后期有空也會寫,
執行到這邊,Mybatis SQL執行基本完了,會把轉換后的結果集回傳到操作者,
結論
在SQL執行程序主要涉及了SqlSession,MapperProxy,Executor,StatementHandler,ParameterHandler以及ResultSetHandler,包括引數動態系結,Sql執行查詢資料庫資料,結果回傳集映射等,而且每個環節涉及的內容都很多,每個介面都可以抽出單獨分析,后續有時間再一一詳細的看看,后面還是再分析一下插件的應用,
各位看官還可以嗎?喜歡的話,動動手指點個💗,點個關注唄!!謝謝支持!
歡迎關注,原創技術文章第一時間推出
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/135251.html
標籤:其他
上一篇:關于YOLO標注問題求助!

