mybatis四大介面之 Executor
阿新 • • 發佈:2018-12-04
【參考文章】:Mybatis-Executor解析
1. Executor的繼承結構
2. Executor(頂層介面)
定義了執行器的一些基本操作;
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) throwsSQLException; // 查詢 <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() throwsSQLException; // 事務提交 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); }
3. BaseExecutor
BaseExecutor是一個抽象類,採用模板方法的設計模式。
它實現了Executor介面,實現了執行器的基本功能。
具體使用哪一個Executor則是可以在 mybatis 的 config.xml 中進行配置的。預設為SimpleExecutor;
配置如下:
<settings> <!--SIMPLE、REUSE、BATCH--> <setting name="defaultExecutorType" value="SIMPLE"/> </settings>
3.1 構造方法
子類的構造方法會呼叫 BaseExecutor 的構造方法。
預設都支援一級快取;
public abstract class BaseExecutor implements Executor { protected BaseExecutor(Configuration configuration, Transaction transaction) { this.transaction = transaction; this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>(); // 一級快取 this.localCache = new PerpetualCache("LocalCache"); this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache"); this.closed = false; this.configuration = configuration; this.wrapper = this; }
}
3.2 update
insert,update,delete操作都會呼叫此方法;
呼叫此方法時會清空一級快取;
@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."); } // 資料變更操作會清空一級快取 clearLocalCache(); return doUpdate(ms, parameter); }
3.3 query
查詢操作會先在快取中查詢,快取命中失敗後再去資料中查詢
@Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); // 建立一級快取的鍵物件 CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); // 呼叫下面的 query 方法 return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } @SuppressWarnings("unchecked") @Override 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; }
3.4 createCacheKey
一級快取通過 HashMap 實現,它的鍵物件根據SQL的ID,引數,SQL本身,分頁引數以及JDBC的引數資訊構成。
@Override // 建立CacheKey物件 public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { if (closed) { throw new ExecutorException("Executor was closed."); } CacheKey cacheKey = new CacheKey(); // MappedStatement的id cacheKey.update(ms.getId()); // 分頁引數的offset cacheKey.update(rowBounds.getOffset()); // 分頁引數的limit cacheKey.update(rowBounds.getLimit()); // SQL語句本身 cacheKey.update(boundSql.getSql()); // 傳遞給jdbc的引數 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); // mimic DefaultParameterHandler logic for (ParameterMapping parameterMapping : parameterMappings) { if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { 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); } cacheKey.update(value); } } if (configuration.getEnvironment() != null) { // issue #176 cacheKey.update(configuration.getEnvironment().getId()); } return cacheKey; }
3.5定義的抽象方法
// 定義的四個抽象方法,在去掉 do 字首的相應方法中被呼叫 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;
4. SimpleExecutor
最簡單的執行器,根據對應的sql直接執行即可,不會做一些額外的操作;
拼接完SQL之後,直接交給 StatementHandler 去執行。
/** * Copyright 2009-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ibatis.executor; import org.apache.ibatis.cursor.Cursor; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.logging.Log; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.transaction.Transaction; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.Collections; import java.util.List; /** * @author Clinton Begin */ public class SimpleExecutor extends BaseExecutor { public SimpleExecutor(Configuration configuration, Transaction transaction) { super(configuration, transaction); } @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); } } @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.<E>queryCursor(stmt); } @Override public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException { return Collections.emptyList(); } 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; } }View Code
5. BatchExecutor
通過批量操作來優化效能。通常需要注意的是批量更新操作,由於內部有快取的實現,使用完成後記得呼叫flushStatements
來清除快取。
public class BatchExecutor extends BaseExecutor { public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002; private final List<Statement> statementList = new ArrayList<Statement>(); private final List<BatchResult> batchResultList = new ArrayList<BatchResult>(); // 上一次的SQL語句 private String currentSql; // 上一次的MappedStatement 物件 private MappedStatement currentStatement; // 因為呼叫父類的構造方法,所以 BatchExecutor 自己的私有屬性 currentSql和currentStatement 開始都為null public BatchExecutor(Configuration configuration, Transaction transaction) { super(configuration, transaction); } @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(); final String sql = boundSql.getSql(); final Statement stmt; // 第一次肯定是false,進入else分支,currentSql和currentStatement被初始化,後面進入false分支則進行更新 if (sql.equals(currentSql) && ms.equals(currentStatement)) { // 取上一次的 Statement 物件 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 { Connection connection = getConnection(ms.getStatementLog()); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); //fix Issues 322 // currentSql和currentStatement 更新為此次的物件 currentSql = sql; currentStatement = ms; statementList.add(stmt); batchResultList.add(new BatchResult(ms, sql, parameterObject)); } // handler.parameterize(stmt); handler.batch(stmt); return BATCH_UPDATE_RETURN_VALUE; } }
6. ReuseExecutor
可重用的執行器,重用的物件是Statement,也就是說該執行器會快取同一個sql的Statement,省去Statement的重新建立,優化效能。 內部的實現是通過一個HashMap來維護Statement物件的。由於當前Map只在該session中有效,所以使用完成後記得呼叫flushStatements
來清除Map。
呼叫實現的四個抽象方法時會呼叫 prepareStatement()
public class ReuseExecutor extends BaseExecutor { private final Map<String, Statement> statementMap = new HashMap<String, Statement>(); // 呼叫父類構造器 public ReuseExecutor(Configuration configuration, Transaction transaction) { super(configuration, transaction); } 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); applyTransactionTimeout(stmt); } else { // 如果沒有快取該SQL,則建立SQL的Statement,並加入快取 Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); putStatement(sql, stmt); } handler.parameterize(stmt); return stmt; } // 是否快取了這個 sql private boolean hasStatementFor(String sql) { try { return statementMap.keySet().contains(sql) && !statementMap.get(sql).getConnection().isClosed(); } catch (SQLException e) { return false; } } // 返回指定sql的 Statement private Statement getStatement(String s) { return statementMap.get(s); } // 新增SQL和Statement private void putStatement(String sql, Statement stmt) { statementMap.put(sql, stmt); } }
7. CachingExecutor
啟用於二級快取時的執行器; 採用靜態代理;代理一個 Executor 物件。 執行 update 方法前判斷是否清空二級快取; 執行 query 方法前先在二級快取中查詢,命中失敗再通過被代理類查詢。public class CachingExecutor implements Executor { // 持有的 Executor,最終的操作都由該物件實現 private final Executor delegate; private final TransactionalCacheManager tcm = new TransactionalCacheManager(); public CachingExecutor(Executor delegate) { this.delegate = delegate; delegate.setExecutorWrapper(this); } public int update(MappedStatement ms, Object parameterObject) throws SQLException { this.flushCacheIfRequired(ms); return this.delegate.update(ms, parameterObject); } 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) { this.flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { this.ensureNoOutParams(ms, boundSql); List<E> list = (List)this.tcm.getObject(cache, key); if (list == null) { list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); this.tcm.putObject(cache, key, list); } return list; } } return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } // 是否清空二級快取 private void flushCacheIfRequired(MappedStatement ms) { Cache cache = ms.getCache(); if (cache != null && ms.isFlushCacheRequired()) { this.tcm.clear(cache); } } }