mybatis源码阅读(五) ---执行器Executor
轉載自??mybatis源碼閱讀(五) ---執行器Executor
1.?Executor接口設計與類結構圖
public interface Executor {ResultHandler NO_RESULT_HANDLER = null;// 執行update,delete,insert三種類型的sql語句int update(MappedStatement ms, Object parameter) throws SQLException;// 執行select類型的SQL語句,返回值分為結果對象列表和游標對象<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;// 批量執行SQL語句List<BatchResult> flushStatements() throws SQLException;// 提交事務void commit(boolean required) throws SQLException;// 事務回滾void rollback(boolean required) throws SQLException;// 創建緩存中用到的CacheKey對象CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);boolean isCached(MappedStatement ms, CacheKey key);// 清空一級緩存void clearLocalCache();// 延遲加載一級緩存中的數據void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);// 獲取事務對象Transaction getTransaction();// 關閉Executor對象void close(boolean forceRollback);// 檢測Executor對象是否關閉boolean isClosed();void setExecutorWrapper(Executor executor);}簡單執行器SimpleExecutor:每執行一次update或select,就開啟一個Statement對象,用完立刻關閉Statement對象。(可以是Statement或PrepareStatement對象)
重用執行器ReuseExecutor:執行update或select,以sql作為key查找Statement對象,存在就使用,不存在就創建,用完后,不關閉Statement對象,而是放置于Map<String, Statement>內,供下一次使用。(可以是Statement或PrepareStatement對象)
批量執行器BatchExecutor:執行update(沒有select,JDBC批處理不支持select),將所有sql都添加到批處理中(addBatch()),等待統一執行(executeBatch()),它緩存了多個Statement對象,每個Statement對象都是addBatch()完畢后,等待逐一執行executeBatch()批處理的;BatchExecutor相當于維護了多個桶,每個桶里都裝了很多屬于自己的SQL,就像蘋果藍里裝了很多蘋果,番茄藍里裝了很多番茄,最后,再統一倒進倉庫。(可以是Statement或PrepareStatement對象)
緩存執行器CachingExecutor:裝飾設計模式典范,先從緩存中獲取查詢結果,存在就返回,不存在,再委托給Executor delegate去數據庫取,delegate可以是上面任一的SimpleExecutor、ReuseExecutor、BatchExecutor。
無用執行器ClosedExecutor:毫無用處,讀者可自行查看其源碼,僅作為一種標識,和Serializable標記接口作用相當。
作用范圍:以上這五個執行器的作用范圍,都嚴格限制在SqlSession生命周期范圍內。
2. 基類BaseExecutor源碼解析
它是一個實現了Executor接口的抽象類,實現了接口中的大部分方法,其中就是使用了模板模式,它主要提供了緩存和事物管理的基本功能,不同的實現類,只要實現4個基本方法來完成數據庫的相關操作,這4個抽象方法:doUpdate()、doQuery()、doFlushStatement()、doQueryCursor。
源碼片段
protected Transaction transaction;// 實現事務的回滾和提交,關閉操作 protected Executor wrapper; // 其中封裝的Executor對象// 延遲加載隊列 protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads; // 下面兩個屬性是一級緩存用到的對象 protected PerpetualCache localCache; protected PerpetualCache localOutputParameterCache;protected Configuration configuration;// 嵌套查詢層級 protected int queryStack; private boolean closed; protected abstract int doUpdate(MappedStatement ms, Object parameter)throws SQLException;protected abstract List<BatchResult> doFlushStatements(boolean isRollback)throws SQLException;protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)throws SQLException;protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)throws SQLException;2.1 SimpleExecutor
@Override public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);stmt = prepareStatement(handler, ms.getStatementLog());return handler.update(stmt);} finally {closeStatement(stmt);} }@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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());return handler.<E>query(stmt, resultHandler);} finally {closeStatement(stmt);//關閉statement對象} }源碼很簡單,從configuration對象中去材料,交給handler去處理,處理完后,statement對象馬上關閉。
2.2 ReuseExecutor
執行器提供了Statement的重用功能,代碼片段如下:
// 緩存使用過的Statement對象,key是SQL語句,value是SQL對應的Statement對象 private final Map<String, Statement> statementMap = new HashMap<String, Statement>(); /*** 準備獲取Statement對象* @param handler* @param statementLog* @return* @throws SQLException*/ private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;BoundSql boundSql = handler.getBoundSql();String sql = boundSql.getSql();if (hasStatementFor(sql)) {// 檢測是否緩存了相同模式的SQL語句所對應的Statement對象stmt = getStatement(sql);// 從緩存中獲取statement對象applyTransactionTimeout(stmt);// 修改超時時間} else {Connection connection = getConnection(statementLog);stmt = handler.prepare(connection, transaction.getTimeout());putStatement(sql, stmt);}handler.parameterize(stmt);return stmt; }那statement對象是什么時候關閉的呢?當事物提交回滾或者關閉時都需要關閉這些緩存的Statement對象,在BaseExecutor.commit(),rollback(),close()方法中都會掉用doFlushStatement()方法,所以在改方法中實現關閉Statement對象是非常合適。具體如下:
@Override public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {// 遍歷map集合并關閉其中的Statement對象for (Statement stmt : statementMap.values()) {closeStatement(stmt);}// 清空緩存statementMap.clear();// 返回空集合return Collections.emptyList(); }2.3 BatchExecutor
BatchExecutor實現了批處理多條SQL語句的功能,需要注意的是在批處理執行SQL語句時,每次向數據庫發送的SQL語句條數是有上限,超過上限會拋出異常,所以批量發送SQL語句的時機是很重要的。
其中的核心字段含義如下:
//緩存多個Statement對象,每個Statement對象中都緩存了多條SQL語句private final List<Statement> statementList = new ArrayList<Statement>();//記錄批處理的結果 BatchResult中通過updateCounts字段(int[])記錄每個Statement執行批處理的結果private final List<BatchResult> batchResultList = new ArrayList<BatchResult>();// 記錄當前執行的SQL語句private String currentSql;// 記錄當前的MappedStatement對象private MappedStatement currentStatement;方法實現如下:
@Override public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {final Configuration configuration = ms.getConfiguration();final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);final BoundSql boundSql = handler.getBoundSql();// 本次執行的sqlfinal String sql = boundSql.getSql();final Statement stmt;// 如果當前執行的SQL與上一次執行的SQL相同且對應的MappedStatement對象相同if (sql.equals(currentSql) && ms.equals(currentStatement)) {int last = statementList.size() - 1;// 已經存在Statement,取出最后一個Statement,有序stmt = statementList.get(last);applyTransactionTimeout(stmt);handler.parameterize(stmt);//fix Issues 322BatchResult batchResult = batchResultList.get(last);batchResult.addParameterObject(parameterObject);} else {// 尚不存在,新建StatementConnection connection = getConnection(ms.getStatementLog());stmt = handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt); //fix Issues 322currentSql = sql;currentStatement = ms;// 放到statementList緩存statementList.add(stmt);batchResultList.add(new BatchResult(ms, sql, parameterObject));} // handler.parameterize(stmt);// 將sql以addBatch()的方式,添加到Statement中(該步驟由StatementHandler內部完成)handler.batch(stmt);return BATCH_UPDATE_RETURN_VALUE; }需要注意的是sql.equals(currentSql)和statementList.get(last),充分說明了其有序邏輯:AABB,將生成2個Statement對象;AABBAA,將生成3個Statement對象,而不是2個。因為,只要sql有變化,將導致生成新的Statement對象。
緩存了這么多Statement批處理對象,何時執行它們?在doFlushStatements()方法中完成執行stmt.executeBatch(),隨即關閉這些Statement對象。
2.4 CachingExecutor
CachingExecutor是一個Executor接口的裝飾器,它為Executor對象增加了二級緩存的相關功能。
//委托的執行器對象,可以是SimpleExecutor、ReuseExecutor、BatchExecutor任一一個 private final Executor delegate; //管理使用的二級緩存對像 private final TransactionalCacheManager tcm = new TransactionalCacheManager();query方法執行的查詢操作步驟:
(1)獲取BoundSql對象,創建查詢語句對應的CacheKey對象,
(2)檢測是否開啟了二級緩存,如果沒有,則指教調用delegate對象的query()方法查詢,如果開啟了,則繼續后面的步驟
(3)檢測查詢是否包含輸出類型的參數,如果是,則報錯
(4)調用TransactionalCacheManager.getObject()方法查詢二級緩存,如果二級緩存中查找到相應的結果,則直接返回結果。
(5)如果二級緩存沒有相應的結果對象,在調用delegate對象的query()方法查詢。最后將得到的結果放入
TransactionalCache.entriesToAddOnCommit集合中保存。
@Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {// (1)BoundSql boundSql = ms.getBoundSql(parameterObject);CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }@Override 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) {// (2)flushCacheIfRequired(ms);if (ms.isUseCache() && resultHandler == null) {// (3)ensureNoOutParams(ms, boundSql);// (4)@SuppressWarnings("unchecked")List<E> list = (List<E>) tcm.getObject(cache, key);if (list == null) {// (5)list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);tcm.putObject(cache, key, list); // issue #578 and #116}return list;}}// 沒有啟動二級緩存,只調用底層Executor查詢return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }?
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的mybatis源码阅读(五) ---执行器Executor的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 这只鼠标学会了自己玩FPS游戏,在训练场
- 下一篇: pdf合并成一个文件如何将pdf合并成一