當咱們讀取完配置文件,將咱們的Mybatis配置成咱們想要的要的樣子以後,咱們就要使用他對數據庫進行一系列操做(增刪改查)。而SqlSession這個看似無所不能的操做達人,實際上是找了代練的。SqlSession將一切數據庫具體操做委託給背後的強者,今天要就讓咱們揭開Executor這個強者的面紗。java
能夠看出強者的家族都是一脈相承的,讓咱們逐一認識一下這一家人。sql
public interface Executor { ResultHandler NO_RESULT_HANDLER = null; int update(MappedStatement ms, Object parameter) throws SQLException; <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; List<BatchResult> flushStatements() throws SQLException; void commit(boolean required) throws SQLException; void rollback(boolean required) throws SQLException; 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(); void close(boolean forceRollback); boolean isClosed(); void setExecutorWrapper(Executor executor); }
Executor接口就好似一個武林祕籍,涵蓋了包括查詢,更新,事務操做,緩存構建的一系列描述,根據方法名咱們可略知一二。得此祕籍者兩人卻分別走了兩條不一樣的修行之路。數據庫
BaseExecutor 做爲一個合格的老師,爲學生鋪好了練習的場地,處理了諸如數據庫鏈接,數據庫關閉,事務回滾,事務提交等一系列繁雜的事情。學生只需專心於如下4技能的修煉便可.關鍵 時刻,學生出拳就能了事。緩存
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;
接下來讓咱們分別看看,他的四大弟子都有什麼本事吧。網絡
SimpleExecutor : 應對簡單處理,每執行一次update或select,就開啓一個Statement對象,用完馬上關閉Statement對象。(能夠是Statement或PrepareStatement對象),俗稱打完就跑,善始善終。mybatis
BatchExecutor:善於批量處理執行update(沒有select,JDBC批處理不支持select),將全部sql都添加到批處理中(addBatch()),等待統一執行(executeBatch()),它緩存了多個Statement對象,每一個Statement對象都是addBatch()完畢後,等待逐一執行executeBatch()批處理的;app
ReuseExecutor :一旦出拳,不叫停不會停。執行update或select,以sql做爲key查找Statement對象,存在就使用,不存在就建立,用完後,不關閉Statement對象,而是放置於Map<String, Statement>內,供下一次使用。ide
ClosedExecutor :最不學無術的一個 ,是ResultLoaderMap的一個內部類,只返回一些標誌位。函數
聽完他們的本事,是否是很想知道他們具體怎麼成爲高徒的呢,秀肌肉到的是時候到了。ui
一、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.query(stmt, resultHandler); } finally { closeStatement(stmt); } } @Override protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql); Statement stmt = prepareStatement(handler, ms.getStatementLog()); Cursor<E> cursor = handler.queryCursor(stmt); stmt.closeOnCompletion(); return cursor; } @Override public List<BatchResult> doFlushStatements(boolean isRollback) { return Collections.emptyList(); }
SimpleExecutor 較爲簡單,不管是query 仍是 update statement隨建隨關。基本步驟就是獲取配置,建立相應的StatementHandler,實例化Statement ,使用StatementHandler執行數據庫操做。
二、ReuseExecutor
private final Map<String, Statement> statementMap = new HashMap<>(); @Override public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); Statement stmt = prepareStatement(handler, ms.getStatementLog()); return handler.update(stmt); } @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); } @Override protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql); Statement stmt = prepareStatement(handler, ms.getStatementLog()); return handler.queryCursor(stmt); }
ReuseExecutor 和 SimpleExecutor 的操做步驟基本類似,最大的區別在於前者維護者一個statementMap 用於記錄並無銷燬的statement,當相同的sql語句被執行時,會使用同一個已經建立好的statement,若是sql第一次執行,那麼就建立一個新的statement,並放入statementMap中。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; BoundSql boundSql = handler.getBoundSql(); String sql = boundSql.getSql(); //有則直接使用 if (hasStatementFor(sql)) { //sql 做爲 key,statement 做爲value stmt = getStatement(sql); applyTransactionTimeout(stmt); } else { // 沒有則建立新的statement Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); putStatement(sql, stmt); } handler.parameterize(stmt); return stmt; }
固然ReuseExecutor 中的statement 不可能一直不關閉,這就須要咱們在恰當的時候,經過調用doFlushStatements方法手動對其進行關閉。
@Override public List<BatchResult> doFlushStatements(boolean isRollback) { for (Statement stmt : statementMap.values()) { closeStatement(stmt); } statementMap.clear(); return Collections.emptyList(); }
三、BatchExecutor
BatchExecutor 是幾個徒弟之中拳法最爲厲害的,他能夠進行批量操做,讓咱們看看他是如何以一敵百的。
讓咱們先來看看BatchExecutor 是處理每一條sql的。
(引用自 網絡資源 「Mybatis3.3.x技術內幕(四):五鼠鬧東京之執行器Executor設計本來」)
BatchExecutor 在執行批量更新時,根據sql語句建立Statement桶,相同的sql使用相同statement,並將全部的statement添加到statementList中。
在執行批量更新的過程當中,並不執行sql,只是拼接不一樣的statement。直至commit時,拿到statementList逐一進行sql執行。
//維護一個Statement列表 用於緩存拼接好的statement private final List<Statement> statementList = new ArrayList<>(); //維護一個Result 列表,用於記錄處理結果 private final List<BatchResult> batchResultList = new ArrayList<>(); //當前保存的sql,也就是上一次執行的sql private String currentSql; ////當前保存的MappedStatement,也就是上一次執行的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(); //要執行的sql final String sql = boundSql.getSql(); final Statement stmt; //若是和上次執行sql相同,同時MappedStatement也必須相同,取上次執行statement,並傳入這次參數 if (sql.equals(currentSql) && ms.equals(currentStatement)) { int last = statementList.size() - 1; stmt = statementList.get(last); applyTransactionTimeout(stmt); handler.parameterize(stmt);//fix Issues 322 BatchResult batchResult = batchResultList.get(last); batchResult.addParameterObject(parameterObject); } else { //若是和上次執行sql不一樣,建立新的statement Connection connection = getConnection(ms.getStatementLog()); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); //fix Issues 322 currentSql = sql; currentStatement = ms; statementList.add(stmt); batchResultList.add(new BatchResult(ms, sql, parameterObject)); } //在handler內部執行addBatch()方法 handler.batch(stmt); return BATCH_UPDATE_RETURN_VALUE; }
須要注意:
sql.equals(currentSql)和statementList.get(last),充分說明了其有序邏輯:AABB,將生成2個Statement對象;AABBAA,將生成3個Statement對象,而不是2個。由於,只要sql有變化,將致使生成新的Statement對象。也就是說,如今執行的sql,只和上一次執行的sql進行對比,是否相同。若是相同使用上一個statement,若是不一樣再次建立新的statement。
那麼何時執行sql 呢?
答案是:在執行commit的時候。在執行commit、rollback等動做前,都將會執行flushStatements()方法,將Statement對象逐一關閉。
看看BatchExecutor .doFlushStatements方法。
@Override public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException { try { List<BatchResult> results = new ArrayList<>(); if (isRollback) { return Collections.emptyList(); } //遍歷statementList,執行sql for (int i = 0, n = statementList.size(); i < n; i++) { Statement stmt = statementList.get(i); applyTransactionTimeout(stmt); BatchResult batchResult = batchResultList.get(i); try { // 執行sql batchResult.setUpdateCounts(stmt.executeBatch()); MappedStatement ms = batchResult.getMappedStatement(); List<Object> parameterObjects = batchResult.getParameterObjects(); KeyGenerator keyGenerator = ms.getKeyGenerator(); if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) { Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator; jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects); } else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { for (Object parameter : parameterObjects) { keyGenerator.processAfter(this, ms, stmt, parameter); } } // 關閉statement closeStatement(stmt); } catch (BatchUpdateException e) { StringBuilder message = new StringBuilder(); message.append(batchResult.getMappedStatement().getId()) .append(" (batch index #") .append(i + 1) .append(")") .append(" failed."); if (i > 0) { message.append(" ") .append(i) .append(" prior sub executor(s) completed successfully, but will be rolled back."); } throw new BatchExecutorException(message.toString(), e, results, batchResult); } //將每次結果添加到resultList中。 results.add(batchResult); } return results; } finally { for (Statement stmt : statementList) { closeStatement(stmt); } currentSql = null; statementList.clear(); batchResultList.clear(); } }
第一次聽到CachingExecutor 的名字的時候,覺得它是全部Executor 中最厲害的一個。看名字就知道CachingExecutor在完成sql執行的任務以外,還作了緩存工做。做爲一個一樣獲得祕籍(直接實現Executor接口)的師叔,其實他就是一個偷奸耍滑的老頭子。
private final Executor delegate; public CachingExecutor(Executor delegate) { this.delegate = delegate; delegate.setExecutorWrapper(this); }
經過他的構造函數咱們能夠清楚的看出,CachingExecutor 其實仍是將全部的sql執行任務交給了不一樣Executor(SimpleExecutor、ReuseExecutor、BatchExecutor),而本身主要進行緩存處理。
關於mybatis的緩存機制,會有單獨的專題進行分析總結
@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) { 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); } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } @Override public int update(MappedStatement ms, Object parameterObject) throws SQLException { flushCacheIfRequired(ms); return delegate.update(ms, parameterObject); } @Override public List<BatchResult> flushStatements() throws SQLException { return delegate.flushStatements(); }