Executor是MyBatis的核心接口之一,其中定義了數據庫操作的基本方法。在實際應用中經常涉及的SqISession接口的功能,都是基於Executor 接口實現的。
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
// 執行update、insert、delete三種類型的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);
// 根據CacheKey對象查找緩存
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();
}
1 BaseExecutor
BaseExecutor是一個實現了Executor接口的抽象類,它實現了Executor接口的大部分方法。BaseExecutor中主要提供了緩存管理和事務管理的基本功能,繼承BaseExecutor的子類只要實現四個基本方法來完成數據庫的相關操作即可,這四個方法分別是:doUpdate()方法、doQuery()方法、doQueryCursor()方法、doFlushStatement()方法。
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
// 事務對象,用於實現事務的提交、回滾和關閉
protected Transaction transaction;
// 其中封裝的Executor對象
protected Executor wrapper;
// 延遲加載隊列
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
// 一級緩存,用於緩存該Executor對象查詢結果集映射得到的結果對象
protected PerpetualCache localCache;
// 一級緩存,用於緩存輸出類型的參數
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
// 記錄嵌套查詢的層數
protected int queryStack;
// 是否關閉
private boolean closed;
}
1.1 一級緩存簡介
常見的應用系統中,數據庫是比較珍貴的資源,很容易成爲整個系統的瓶頸。在設計和維護系統時,會進行多方面的權衡,並且利用多種優化手段,減少對數據庫的直接訪問。
使用緩存是一種比較有效的優化手段,使用緩存可以減少應用系統與數據庫的網絡交互、減少數據庫訪問次數、降低數據庫的負擔、降低重複創建和銷燬對象等一系列開銷,從而提高整個系統的性能。
MyBatis提供的緩存功能,分別爲一級緩存和二級緩存。BaseExecutor主要實現了一級緩存的相關內容。一級緩存是會話級緩存,在MyBatis中每創建一個SqlSession對象,就表示開啓一次數據庫會話。在一次會話中,應用程序可能會在短時間內(一個事務內),反覆執行完全相同的查詢語句,如果不對數據進行緩存,那麼每一次查詢都會執行一次數據庫查詢操作,而多次完全相同的、時間間隔較短的查詢語句得到的結果集極有可能完全相同,這會造成數據庫資源的浪費。
爲了避免上述問題,MyBatis會在Executor對象中建立一個簡單的一級緩存,將每次查詢的結果集緩存起來。在執行查詢操作時,會先查詢一級緩存,如果存在完全一樣的查詢情況,則直接從一級緩存中取出相應的結果對象並返回給用戶,減少數據庫訪問次數,從而減小了數據庫的壓力。
一級緩存的生命週期與SqlSession相同,其實也就與SqISession中封裝的Executor 對象的生命週期相同。當調用Executor對象的close()方法時(斷開連接),該Executor 對象對應的一級緩存就會被廢棄掉。一級緩存中對象的存活時間受很多方面的影響,例如,在調用Executor的update()方法時,也會先請空一級緩存。一級緩存默認是開啓的,一般情況下,不需要用戶進行特殊配置。
1.2 一級緩存的管理
BaseExecutor的query()方法會首先創建CacheKey對象,並根據該CacheKey對象查找一級緩存,如果緩存命中則返回緩存中記錄的結果對象,如果緩存未命中則查詢數據庫得到結果集,之後將結果集映射成結果對象並保存到一級緩存中,同時返回結果對象。
public abstract class BaseExecutor implements Executor {
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 獲取BoundSql對象
BoundSql boundSql = ms.getBoundSql(parameter);
// 創建CacheKey對象,該對象由多個參數組裝而成
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// query方法的重載,進行後續處理
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 可以看到CacheKey對象由MappedStatement的id、RowBounds的offset和limit
// sql語句(包含佔位符"?")、用戶傳遞的實參組成
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// 獲取用戶傳入的實參,並添加到CacheKey對象中
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對象中
cacheKey.update(value);
}
}
// 如果configuration的environment不爲空,則將該environment的id
// 添加到CacheKey對象中
if (configuration.getEnvironment() != null) {
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 檢查當前Executor是否已關閉
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()) {
// 非嵌套查詢,且<select>節點配置的flushCache屬性爲true時,纔會清空一級緩存
clearLocalCache();
}
List<E> list;
try {
// 增加查詢層數
queryStack++;
// 根據傳入的CacheKey對象 查詢一級緩存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 針對存儲過程調用的處理,在一級緩存命中時,獲取緩存中保存的輸出類型參數
// 並設置到用戶傳入的實參parameter對象中
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 緩存未命中,則從數據庫查詢結果集,其中會調用doQuery()方法完成數據庫查詢操作,
// 該方法爲抽象方法,由BaseExecutor的子類實現
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
// 當前查詢完成,查詢層數減少
queryStack--;
}
if (queryStack == 0) {
// 延遲加載的相關內容
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
}
從上面的代碼中可以看到,BaseExecutor的query()方法會根據flushCache屬性和localCacheScope配置 決定是否清空一級緩存。
另外,BaseExecutor的update()方法在調用doUpdate()方法之前,也會清除一級緩存。update()方法負責執行insert、update、delete三類SQL 語句,它是調用doUpdate()方法實現的。
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
// 判斷當前的Executor是否已經關閉
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清除一級緩存,該方法會調用localCache和localOutputParameterCache
// 的clear()方法清除緩存
clearLocalCache();
// 抽象方法,交由子類實現
return doUpdate(ms, parameter);
}
@Override
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}
1.3 事務相關操作
在BatchExecutor實現中,可以緩存多條SQL語句,等待合適時機將緩存的多條SQL 語句一併發送到數據庫執行。Executor的flushStatements()方法主要是針對批處理多條SQL語句的,它會調用doFlushStatements()這個基本方法處理Executor中緩存的多條SQL語句。在BaseExecutor的commit()及rollback()等方法中都會首先調用flushStatements()方法,然後再執行相關事務操作。
@Override
public void commit(boolean required) throws SQLException {
// 檢查當前連接是否已關閉
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
// 清除一級緩存
clearLocalCache();
// 不執行Executor中緩存的SQL語句
flushStatements();
// 根據參數required決定是否提交事務
if (required) {
transaction.commit();
}
}
@Override
public List<BatchResult> flushStatements() throws SQLException {
return flushStatements(false);
}
public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 這是一個交由子類實現的抽象方法,參數isRollBack表示
// 是否執行Executor中緩存的SQL語句,false表示執行,true表示不執行
return doFlushStatements(isRollBack);
}
@Override
public void rollback(boolean required) throws SQLException {
if (!closed) {
try {
// 清除一級緩存
clearLocalCache();
// 批量執行緩存的sql語句
flushStatements(true);
} finally {
// 根據required決定是否回滾事務
if (required) {
transaction.rollback();
}
}
}
}
@Override
public void close(boolean forceRollback) {
try {
try {
// 根據forceRollback參數決定 是否強制回滾該事務
rollback(forceRollback);
} finally {
if (transaction != null) {
transaction.close();
}
}
} catch (SQLException e) {
// Ignore. There's nothing that can be done at this point.
log.warn("Unexpected exception on closing transaction. Cause: " + e);
} finally {
transaction = null;
deferredLoads = null;
localCache = null;
localOutputParameterCache = null;
closed = true;
}
}
2 SimpleExecutor
SimpleExecutor繼承了BaseExecutor抽象類,它是最簡單的Executor接口實現。Executor組件使用了模板方法模式,一級緩存等固定不變的操作都封裝到了BaseExecutor中,在SimpleExecutor中就不必再關心一級緩存等操作,只需要專注實現4 個基本方法的實現即可。
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@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的創建和初始化,該方法首先會調用StatementHandler的prepare()方法
// 創建Statement對象,然後調用StatementHandler的parameterize()方法處理佔位符
stmt = prepareStatement(handler, ms.getStatementLog());
// 調用StatementHandler的query()方法,執行sql語句,並通過ResultSetHandler
// 完成結果集的映射
return handler.<E>query(stmt, resultHandler);
} finally {
// 關閉Statement對象
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
// 創建Statement對象
stmt = handler.prepare(connection, transaction.getTimeout());
// 處理佔位符
handler.parameterize(stmt);
return stmt;
}
/**
* 與前面doQuery()方法的實現非常類似
*/
@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
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 {
// SimpleExecutor不提供sql語句批處理,所以直接返回空集合
return Collections.emptyList();
}
}
3 ReuseExecutor
在傳統的JDBC編程中,複用Statement對象是常用的一種優化手段,該優化手段可以減少SQL預編譯的開銷以及創建和銷燬Statement對象的開銷,從而提高性能(Reuse,複用)。
ReuseExecutor提供了Statement複用的功能,ReuseExecutor中通過statementMap 字段緩存使用過的Statement對象,key是SQL語句,value是SQL對應的Statement 對象。
ReuseExecutor.doQuery()、doQueryCursor()、doUpdate()方法的實現與SimpleExecutor中對應方法的實現一樣,區別在於其中調用的prepareStatement()方法,SimpleExecutor每次都會通過JDBC的Connection對象創建新的Statement對象,而ReuseExecutor則會先嚐試重用StaternentMap中緩存的Statement對象。
// 本map用於緩存使用過的Statement,以提升本框架的性能
// key SQL語句,value 該SQL語句對應的Statement
private final Map<String, Statement> statementMap = new HashMap<String, Statement>();
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
// 獲取要執行的sql語句
String sql = boundSql.getSql();
// 如果之前執行過該sql,則從緩存中取出對應的Statement對象
// 不再創建新的Statement,減少系統開銷
if (hasStatementFor(sql)) {
stmt = getStatement(sql);
// 修改超時時間
applyTransactionTimeout(stmt);
} else {
// 獲取數據庫連接
Connection connection = getConnection(statementLog);
// 從連接中獲取Statement對象
stmt = handler.prepare(connection, transaction.getTimeout());
// 將sql語句 和 其對應的Statement對象緩存起來
putStatement(sql, stmt);
}
// 處理佔位符
handler.parameterize(stmt);
return stmt;
}
/**
* 當事務提交或回滾、連接關閉時,都需要關閉這些緩存的Statement對象。前面分析的BaseExecutor的
* commit()、rollback()和close()方法中都會調用doFlushStatements()方法,
* 所以在該方法中關閉Statement對象的邏輯非常合適
*/
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
// 遍歷Statement對象集合,並依次關閉
for (Statement stmt : statementMap.values()) {
closeStatement(stmt);
}
// 清除對Statement對象的緩存
statementMap.clear();
// 返回一個空集合
return Collections.emptyList();
}
拓展內容:SQL預編譯
1、數據庫預編譯起源
(1)數據庫SQL語句編譯特性
數據庫接收到sql語句之後,需要詞法和語義解析,以優化sql語句,制定執行計劃。這需要花費一些時間。但是很多情況,我們的同一條sql語句可能會反覆執行,或者每次執行的時候只有個別的值不同(比如:query的where子句值不同,update的set子句值不同,insert的values值不同)。
(2)減少編譯的方法
如果每次都需要經過上面的詞法語義解析、語句優化、制定執行計劃等,則效率就明顯不行了。爲了解決上面的問題,於是就有了預編譯,預編譯語句就是將這類語句中的值用佔位符替代,可以視爲將sql語句模板化或者說參數化。一次編譯、多次運行,省去了解析優化等過程。
(3)緩存預編譯
預編譯語句被DB的編譯器編譯後的執行代碼被緩存下來,那麼下次調用時只要是相同的預編譯語句就不需要重複編譯,只要將參數直接傳入編譯過的語句執行代碼中(相當於一個函數)就會得到執行。並不是所以預編譯語句都一定會被緩存,數據庫本身會用一種策略(內部機制)。
(4) 預編譯的實現方法
預編譯是通過PreparedStatement和佔位符來實現的。
2.預編譯作用
(1)減少編譯次數 提升性能
預編譯之後的 sql 多數情況下可以直接執行,DBMS(數據庫管理系統)不需要再次編譯。越複雜的sql,往往編譯的複雜度就越大。
(2)防止SQL注入
使用預編譯,後面注入的參數將不會再次觸發SQL編譯。也就是說,對於後面注入的參數,系統將不會認爲它會是一個SQL命令,而默認其是一個參數,參數中的or或and等(SQL注入常用技倆)就不是SQL語法保留字了。
3.mybatis是如何實現預編譯的
mybatis默認情況下,將對所有的 sql 進行預編譯。mybatis底層使用PreparedStatement,過程是,先將帶有佔位符(即”?”)的sql模板發送至數據庫服務器,由服務器對此無參數的sql進行編譯後,將編譯結果緩存,然後直接執行帶有真實參數的sql。核心是通過 “#{ }” 實現的。在預編譯之前,#{ } 被解析爲一個預編譯語句(PreparedStatement)的佔位符 ?。
// sqlMap 中如下的 sql 語句
select * from user where name = #{name};
// 解析成爲預編譯語句
select * from user where name = ?;
4 BatchExecutor
應用系統在執行一條SQL語句時,會將SQL語句以及相關參數通過網絡發送到數據庫系統。對於頻繁操作數據庫的應用系統來說,如果執行一條SQL語句就向數據庫發送一次請求,很多時間會浪費在網絡通信上。使用批量處理的優化方式可以在客戶端緩存多條SQL語句,並在合適的時機將多條SQL語句打包發送給數據庫執行,從而減少網絡方面的開銷,提升系統的性能。
需要注意的是,在批量執行多條SQL 語句時,每次向數據庫發送的SQL語句條數
是有上限的,若超出上限,數據庫會拒絕執行這些SQL語句井拋出異常,所以批量發送SQL語句的時機很重要。
mybatis的BatchExecutor實現了批處理多條SQL 語句的功能。
public class BatchExecutor extends BaseExecutor {
public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;
// 緩存多個Statement對象,其中每個Statement對象中都可以緩存多條
// 結構相同 但參數不同的sql語句
private final List<Statement> statementList = new ArrayList<Statement>();
// 記錄批處理的結果,BatchResult中通過updateCounts字段
// 記錄每個Statement對象 執行批處理的結果
private final List<BatchResult> batchResultList = new ArrayList<BatchResult>();
// 記錄當前執行的sql語句
private String currentSql;
// 記錄當前執行的MappedStatement對象
private MappedStatement currentStatement;
/**
* JDBC中的批處理只支持insert、update、delete等類型的SQL語句,不支持select類型的
* SQL語句,所以doUpdate()方法是BatchExecutor中最重要的一個方法。
* 本方法在添加一條SQL語句時,首先會將currentSql字段記錄的SQL語句以及currentStatement字段
* 記錄的MappedStatement對象與當前添加的SQL以及MappedStatement對象進行比較,
* 如果相同則添加到同一個Statement對象中等待執行,如果不同則創建新的Statement對象
* 井將其緩存到statementList集合中等待執行
*/
@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();
final String sql = boundSql.getSql();
final Statement stmt;
// 判斷要執行的sql語句結構 及 MappedStatement對象 是否與上次的相同
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
// 相同則添加到同一個Statement對象中等待執行
// 首先獲取statementList集合中最後一個Statement對象
int last = statementList.size() - 1;
stmt = statementList.get(last);
// 重新設置事務超時時間
applyTransactionTimeout(stmt);
// 綁定實參,處理佔位符?
handler.parameterize(stmt);
// 查找對應的BatchResult對象,並記錄用戶傳入的實參
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
} else {
// 不同則創建新的Statement對象井將其緩存到statementList集合中等待執行
Connection connection = getConnection(ms.getStatementLog());
// 創建新的Statement對象
stmt = handler.prepare(connection, transaction.getTimeout());
// 綁定實參,處理佔位符?
handler.parameterize(stmt);
// 記錄本次的sql語句 及 Statement對象
currentSql = sql;
currentStatement = ms;
// 將新創建的Statement對象添加到statementList集合
statementList.add(stmt);
// 添加新的BatchResult對象
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
// 底層通過調用java.sql.Statement的addBatch()方法添加sql語句
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
/**
* 上面的doUpdate()方法負責添加待執行的sql語句,
* 而doFlushStatements()方法則將上面添加的sql語句進行批量處理
*/
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
try {
// 用於存儲批處理結果的集合
List<BatchResult> results = new ArrayList<BatchResult>();
// 如果要回滾 則返回一個空集合
if (isRollback) {
return Collections.emptyList();
}
// 批處理statementList集合中的所以Statement對象
for (int i = 0, n = statementList.size(); i < n; i++) {
// 獲取Statement對象 和其對應的 BatchResult對象
Statement stmt = statementList.get(i);
applyTransactionTimeout(stmt);
BatchResult batchResult = batchResultList.get(i);
try {
// 調用Statement對象的executeBatch()方法,批量執行其中記錄的sql語句
// 將執行返回的int[]數組set進batchResult的updateCounts字段,
// 其中的每一個int值都代表了對應的sql語句 影響的記錄條數
batchResult.setUpdateCounts(stmt.executeBatch());
MappedStatement ms = batchResult.getMappedStatement();
List<Object> parameterObjects = batchResult.getParameterObjects();
// 獲取配置的KeyGenerator對象
KeyGenerator keyGenerator = ms.getKeyGenerator();
if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {
Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
// 獲取數據庫生成的主鍵 並設置到parameterObjects中
jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
} else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) {
// 對於其它類型的KeyGenerator,則調用其processAfter進行處理
for (Object parameter : parameterObjects) {
keyGenerator.processAfter(this, ms, stmt, parameter);
}
}
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);
}
// 添加處理完的BatchResult對象到要返回的List<BatchResult>集合中
results.add(batchResult);
}
return results;
} finally {
// 關閉所有的Statement對象
for (Statement stmt : statementList) {
closeStatement(stmt);
}
// 清空currentSql、statementList、batchResultList對象
currentSql = null;
statementList.clear();
batchResultList.clear();
}
}
}
通過了解JDBC的批處理功能 我們可以知道,Statement中可以添加不同語句結構的SQL,但是每添加一個新結構的SQL語句都會觸發一次編譯操作。而PreparedStatement中只能添加同一語句結構的SQL語句,只會觸發一次編譯操作,但是可以通過綁定多組不同的實參實現批處理。通過上面對doUpdate()方法的分析可知,BatchExecutor會將連續添加的、相同語句結構的SQL語句添加到同一個Statement/PreparedStatement對象中,這樣可以有效地減少編譯操作的次數。
BatchExecutor中doQuery()和doQueryCursor()方法的實現與前面介紹的SimpleExecutor類似,主要區別就是BatchExecutor中的這兩個方法在最開始都會先調用flushStatements()方法,執行緩存的SQL語句,以保證 從數據庫中查詢到的數據是最新的。
CachingExecutor中爲Executor對象增加了二級緩存相關功能,而mybatis的二級緩存在實際使用中往往利大於弊,被redis等產品所替代,所以這裏不做分析。