<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                上一篇文章《Mybatis源碼之SqlSession》聊到了SqlSession其實是一個包工頭兒,攬了活自己不干都安排給了執行器;而且在《Mybatis源碼之SQL執行過程》中已經了解到,執行器通過對StatementHandler生命周期的調度與管理,最終完成SQL命令執行。 今天我們就來認識一下Mybatis的四大組件之一:Executor-執行器。提前說明一下,由于Executor涉及到Mybatis的一級緩存、二級緩存,這部分內容后面會單獨分析,本文先不展開。 ### Executor簡介 Executor位于executor包,Mybatis中所有的SQL命令都由它來調度執行。首先通過UML類圖認識一下Executor家族: ![image.png](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8018eff4a24c4e8a967508889897feeb~tplv-k3u1fbpfcp-zoom-1.image) * 最頂層是Executor接口,定義了查詢、更新、事務、緩存操作相關的接口方法,Executor接口對外暴露,由SqlSession依賴,并受其調度與管理。 * 第二層左側為BaseExecutor,它是一個抽象類,實現了大部分Executor的接口。它有三個子類,分別是SimpleExecutor、ReuseExecutor、BatchExecutor。BaseExecutor及其子類完成了一級緩存管理和與數據庫交互有關的操作。 * 第二層右側為CachingExecutor,緩存執行器,Mybatis二級緩存的核心處理類。CachingExecutor持有一個BaseExecutor的實現類(SimpleExecutor、ReuseExecutor或BatchExecutor)實例作為委托執行器。它主要完成Mybatis二級緩存處理邏輯,當緩存查詢中不存在或查詢不到結果時,會通過委托執行器查詢數據庫。 * 第三層就是BaseExecutor的三個子類。簡單執行器為默認執行器,具備執行器的所有能力;可重用執行器,是相對簡單執行器而言的,它具備MappedStatement的緩存與復用能力,即在一個SqlSession會話內重復執行同一個命令,可以直接復用已緩存的MappedStatement;批量執行器,即一次可以執行多個命令。 Executor的核心功能是調度執行SQL,參與了全過程;為了提高查詢性能,Mybatis在Executor中設計了一級緩存和二級緩存,一級緩存由BaseExecutor及其子類實現,二級緩存由CachingExecutor實現。一級緩存是默認開啟的,二級緩存需要啟用配置。由于CachingExecutor負責緩存管理,真正的數據庫查詢是由BaseExecutor完成的,所以對外來看,Executor有三種類型SIMPLE、REUSE、BATCH,默認是SIMPLE,我們可以在配置文件或創建SqlSession時指定參數修改默認執行器類型。以下為Mybatis中ExecutorType定義: ~~~java public enum ExecutorType { // 簡單執行器 SIMPLE, // 可重用的執行器 REUSE, // 批處理執行器 BATCH } 復制代碼 ~~~ ### 創建過程分析 前面已經說到,SqlSession依賴Executor,Executor接受SqlSession的請求并執行,而且Executor的創建是隨著SqlSession的創建而創建的。Executor的創建流程始于DefaultSqlSessionFactory#openSession()及其重載方法,代碼如下: ~~~java @Override public SqlSession openSession() { //這里使用默認的執行器類型:SIMPLE return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } 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); //通過configuration.newExecutor方法創建執行器 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(); } } 復制代碼 ~~~ 簡單分析一下這段代碼:這里采用了無參openSession方法,其內部使用Configuration#defaultExecutorType作為執行器類型調用openSessionFromDataSource方法;后者對execType未做處理調用了Configuration#newExecutor()執行Executor創建流程。 Configuration#defaultExecutorType默認值為SIMPLE,它會在Mybatis解析配置文件時被修改。若配置文件未涉及executorType的配置,默認值不會改變。另外,可以調用openSession的重載方法指定執行器類型。兩種設置方式如下所示: * Mybatis默認(缺省)的執行器類型是SIMPLE,我們可以在配置文件中進行修改設置: ~~~xml <settings> <setting name="defaultExecutorType" value="SIMPLE"/> </settings> 復制代碼 ~~~ * 或者通過代碼進行設置: ~~~java // 指定執行器類型為REUSE try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE)) { //…… } 復制代碼 ~~~ 了解了ExecutorType的設置方式,接著看Configuration#newExecutor()方法: ~~~java //Executor執行器創建方法 public Executor newExecutor(Transaction transaction, ExecutorType executorType) { //executorType為null:使用默認執行器,否則按照入參類型 //看Configuration代碼,其實有: protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE; executorType = executorType == null ? defaultExecutorType : executorType; //executorType和defaultExecutorType都為null,默認使用SIMPLE executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; //按照executorType創建不同的執行器。 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); } //這里判斷是否開啟了二級緩存,默認值是true。 //所以如果沒有修改配置文件的話,這個if語句是會進入執行的 if (cacheEnabled) { //創建緩存執行器,同時把上面創建的executor作為其委托執行器 executor = new CachingExecutor(executor); } //這里是加載與Executor有關的插件。 executor = (Executor) interceptorChain.pluginAll(executor); return executor; } 復制代碼 ~~~ 總結一下執行過程: * 首先:處理執行器類型參數,確保其不為空,最終以SIMPLE兜底; * 其次:根據執行器類型調用對應的執行器實現類做初始化,執行器可以為SimpleExecutor、ReuseExecutor、BatchExecutor中的一個; * 然后:如果啟用了二級緩存(cacheEnabled默認是true,但是如果不設置cache的相關參數,二級緩存是不起作用的),創建緩存執行器,同時把上面創建的executor作為緩存執行器的委托執行器; * 最后:加載與Executor有關的插件。 默認情況下,會返回一個CachingExecutor對象,其內部包裝了BaseExecutor的某個子類對象。 ![image.png](data:image/svg+xml;utf8,) ### query流程 Executor接口中query方法有兩個重載,我們從參數少的這個開始入手,它內部也會調用到另外一個。默認情況下會使用CachingExecutor,所以我們從CachingExecutor#query()開始: ~~~java public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // 獲取BoundSql BoundSql boundSql = ms.getBoundSql(parameterObject); // 創建緩存key CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); // 調用重載query方法 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.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } //默認情況沒有開啟二級緩存,會直接走到這里 //delegate即BaseExecutor三個子類的其中一個 return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } 復制代碼 ~~~ 進入CachingExecutor#query,首先通過MappedStatement獲取BoundSql,創建緩存key,然后調用了重載的query方法。重載query查詢在不考慮緩存的情況下,會直接通過委托執行器的query方法進行查詢。 這里的委托執行器為BaseExecutor的子類,而BaseExecutor實現了query方法,所以我們先進入BaseExecutor#query()(同樣先忽略一級緩存部分的邏輯): ~~~java 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 (closed) { throw new ExecutorException("Executor was closed."); } if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { queryStack++; //從一級緩存(本地緩存)中查詢 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { //緩存中不存在,從數據庫中查詢 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; } private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; //緩存占位 localCache.putObject(key, EXECUTION_PLACEHOLDER); try { //調用抽象方法執行數據庫查詢,子類實現 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { //移除占位 localCache.removeObject(key); } //設置緩存 localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; } 復制代碼 ~~~ 兩個方法比較長,大部分是緩存處理。忽略緩存的情況下(直接看我注釋的部分),從query方法調用了數據庫查詢方法queryFromDatabase,但是,真正查詢的邏輯是在抽象方法doQuery中實現的,doQuery由BaseExecutor子類實現。我們依次看下子類實現邏輯: #### SimpleExecutor#doQuery ~~~java @Override 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 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); //準備Statement stmt = prepareStatement(handler, ms.getStatementLog()); //執行查詢 return handler.query(stmt, resultHandler); } finally { //關閉Statement closeStatement(stmt); } } 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; } 復制代碼 ~~~ SimpleExecutor#doQuery方法代碼比較簡單,過程清晰明了,簡單說明一下: * 從MappedStatement對象獲取全局Configuration配置對象; * 調用Configuration#newStatementHandler創建StatementHandler對象; * 創建并初始化Statement對象; * 調用StatementHandler#query執行Statement,并使用resultHandler解析返回值; * 最后關閉Statement。 從上述流程可知,doQuery方法調度StatementHandler完成了對Statement的初始化、參數設置、執行、結果處理與關閉,是對Statement整個生命周期的管理與控制,與前文所說的Executor參與了SQL語句執行的全過程名副其實。 #### ReuseExecutor#doQuery ~~~java //Statement緩存 private final Map<String, Statement> statementMap = new HashMap<>(); @Override public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); Statement stmt = prepareStatement(handler, ms.getStatementLog()); return handler.query(stmt, resultHandler); } private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; BoundSql boundSql = handler.getBoundSql(); String sql = boundSql.getSql(); //檢查緩存中是否存在當前sql if (hasStatementFor(sql)) { //如果有,就直接拿出來用 stmt = getStatement(sql); applyTransactionTimeout(stmt); } else { //如果沒有,就新建一個 Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); //然后,緩存起來。 putStatement(sql, stmt); } handler.parameterize(stmt); return stmt; } 復制代碼 ~~~ ReuseExecutor#doQuery與SimpleExecutor#doQuery的邏輯基本一致,不同點在于prepareStatement方法的實現邏輯。prepareStatement使用statementMap對執行過的sql進行緩存,只有statementMap中不存在當前sql的時候才會執行創建流程,對性能有一定的提升。需要注意的是,Executor對象是SqlSession的組成部分,所以這個緩存與SqlSession的生命周期一致。 #### BatchExecutor#doQuery ~~~java @Override public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { //批量執行,目前我理解是為了把之前批量更新的語句執行掉 flushStatements(); //獲取Configuration對象 Configuration configuration = ms.getConfiguration(); //創建StatementHandler StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql); //創建Statement Connection connection = getConnection(ms.getStatementLog()); stmt = handler.prepare(connection, transaction.getTimeout()); //設置Statement參數 handler.parameterize(stmt); //執行并返回結果 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } } 復制代碼 ~~~ BatchExecutor#doQuery方法除了多執行flushStatements方法外,與SimpleExecutor基本一致,不再展開。 ### update()流程 update()方法的對應了insert、update、delete等不同的命令,其執行流程與query()方法流程類似。同樣是經過CachingExecutor->BaseExecutor->SimpleExecutor/ReuseExecutor/BatchExecutor。把整個過程的代碼一起貼出來分析(注釋): ~~~java //org.apache.ibatis.executor.CachingExecutor#update @Override public int update(MappedStatement ms, Object parameterObject) throws SQLException { // 刷新緩存:開啟緩存時,update命令默認情況是需要刷新緩存的 flushCacheIfRequired(ms); //調用委托執行器進行update return delegate.update(ms, parameterObject); } //org.apache.ibatis.executor.BaseExecutor#update @Override public int update(MappedStatement ms, Object parameter) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } // 清空緩存,然后調用子類的doUpdate方法 clearLocalCache(); //調用抽象方法執行數據庫更新操作 return doUpdate(ms, parameter); } 復制代碼 ~~~ Mybatis的緩存機制僅對查詢有效,所以Executor能做的就是:使緩存失效、請求中轉,最終調用doUpdate執行數據庫操作,所以: * CachingExecutor#update首先使二級緩存失效,然后調用委托執行器執行update操作。 * BaseExecutor#update也是首先使得一級緩存失效,然后調用抽象方法doUpdate執行數據庫的更新操作。 #### SimpleExecutor#doUpdate SimpleExecutor#doUpdate與doQuery完全一致,不再說明了。 #### ReuseExecutor#doUpdate ReuseExecutor#doUpdate與doQuery完全一致,不再說明了。 #### BatchExecutor#doUpdate BatchExecutor的執行與前兩個不一樣,它用于執行批量的sql命令,所以多了一些批量準備工作。為了減少與數據庫的交互次數,BatchExecutor會批量執行sql命令。代碼如下: ~~~java @Override public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException { //獲取Configuration對象 final Configuration configuration = ms.getConfiguration(); //創建StatementHandler final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null); //獲取sql對象 final BoundSql boundSql = handler.getBoundSql(); //獲取sql與酒 final String sql = boundSql.getSql(); final Statement stmt; //如果當前命令與上一次執行一樣,就不再重復創建Statement,性能提升 if (sql.equals(currentSql) && ms.equals(currentStatement)) { //取出最后一條的索引 int last = statementList.size() - 1; //取出最后一個Statement對象 stmt = statementList.get(last); applyTransactionTimeout(stmt); //設置Statement參數 handler.parameterize(stmt);//fix Issues 322 //獲取BatchResult BatchResult batchResult = batchResultList.get(last); //設置BatchResult參數對象 batchResult.addParameterObject(parameterObject); } else { //如果當前命令與上一次執行不一樣,重新創建 Connection connection = getConnection(ms.getStatementLog()); //初始化、準備Statement stmt = handler.prepare(connection, transaction.getTimeout()); //設置參數 handler.parameterize(stmt); //fix Issues 322 //設置當前執行的sql命令信息 currentSql = sql; currentStatement = ms; //存起來 statementList.add(stmt); //保存結果對象 batchResultList.add(new BatchResult(ms, sql, parameterObject)); } //批量處理 handler.batch(stmt); return BATCH_UPDATE_RETURN_VALUE; } 復制代碼 ~~~ BatchExecutor#doUpdate方法完成了Statement執行前的準備工作,在準備Statement時與上一次要執行的Statement進行對比,如果一致則不再執行重新創建Statement的流程。所以,使用BatchExecutor時應該盡量執行相同的sql命令。 但是,BatchExecutor#doUpdate并未進行數據庫的執行操作,它需要通過SqlSession#flushStatements進行觸發,然后調用到BatchExecutor#doFlushStatements執行最終的操作,這里就不再展開了。 ### Executor總結 本文從源碼角度對Executor的創建流程、query流程、update流程進行了詳細梳理,了解了Executor執行update/query的執行流程,重點說明了其與數據庫交互的過程,即Statement的調度管理,Executor的緩存機制我們并沒有展開。Executor是Mybatis四大組件之一,雖然還沒有研究其他三個,但是已經對Mybatis的SQL執行過程有了整體的了解,由此可見Executor的作用舉足輕重。 作者:碼路印記 鏈接:https://juejin.cn/post/6883840514861301773 來源:掘金 著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看