在瞭解了MyBatis初始化加載過程後,咱們也應該研究看看SQL執行過程是怎樣執行?這樣咱們對於Mybatis的整個執行流程都熟悉了,在開發遇到問題也能夠很快定位到問題。java
更重要的,在面試中遇到面試官諮詢Mybatis的知識點的時候,能夠很順暢的把這一套流程講出來,面試官也會以爲你已掌握Mybatis知識點了,可能就不問了。趕忙瞄瞄面試
通過MyBatis初始化加載Sql執行過程所需的信息後,咱們就能夠經過 SqlSessionFactory
對象獲得 SqlSession
,而後執行 SQL 語句了,接下來看看Sql執行具體過程,SQL大體執行流程圖以下所示:sql
接下來咱們來看看每一個執行鏈路中的具體執行過程,數據庫
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
。mybatis
MapperProxy
是 Mapper
接口與SQL 語句映射的關鍵,經過 MapperProxy
可讓對應的 SQL 語句跟接口進行綁定的,具體流程以下:app
MapperProxy
代理類生成流程MapperProxy
代理類執行操做MapperProxy
代理類生成流程ide
其中,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
對象爲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
負責處理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
參數處理器, 用來設置參數規則的,負責爲sql 語句參數動態賦值,其有兩個接口
當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
結果解析器,將查詢結果的ResultSet
轉換成映射的對應結果(java DTO
等),其有三接口
handleResultSets()
:處理結果集handleCursorResultSets()
:批量處理結果集handleOutputParameters()
:處理存儲過程返回的結果集其默認的實現爲DefaultResultSetHandler
,主要功能爲:
Statement
執行後產生的結果集生成相對的輸出結果、那看看DefaultResultSetHandler::handleResultSets()
如何處理?
ResultSet
的結果集合,每一個ResultSet對應一個Object 對象,若是不考慮存儲過程,普通的查詢只有一個ResultSetResultSetWrapper
封裝了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執行查詢數據庫數據,結果返回集映射等,並且每一個環節涉及的內容都不少,每一個接口均可以抽出單獨分析,後續有時間再一一詳細的看看。後面仍是再分析一下插件的應用。
各位看官還能夠嗎?喜歡的話,動動手指點個贊💗唄!!謝謝支持!
歡迎掃碼關注,原創技術文章第一時間推出
![]()