MyBatis原理系列(一)-手把手帶你閱讀MyBatis源碼
MyBatis原理系列(二)-手把手帶你了解MyBatis的啟動流程
MyBatis原理系列(三)-手把手帶你了解SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的關(guān)系
MyBatis原理系列(四)-手把手帶你了解MyBatis的Executor執(zhí)行器
MyBatis原理系列(五)-手把手帶你了解Statement、StatementHandler、MappedStatement間的關(guān)系
MyBatis原理系列(六)-手把手帶你了解BoundSql的創(chuàng)建過程
MyBatis原理系列(七)-手把手帶你了解如何自定義插件
MyBatis原理系列(八)-手把手帶你了解一級緩存和二級緩存
MyBatis原理系列(九)-手把手帶你了解MyBatis事務(wù)管理機制
上篇文章中我們對SqlSession有了比較詳細(xì)的講解坟比,SqlSession執(zhí)行的方法其實都交由Executor進行執(zhí)行代箭。Executor執(zhí)行器作為MyBatis的重要組件之一伍宦,也是有著比較優(yōu)秀的設(shè)計和復(fù)雜的細(xì)節(jié)。在這篇文章將會圍繞Executor來進行抽絲剝繭,探討Executor的原理。
1. Executor初識
Executor 作為一個接口碘菜,包含更新,查詢限寞,事務(wù)等一系列方法忍啸。每個SqlSession對象都會有一個Executor對象,SqlSession的操作都會由Executor執(zhí)行器執(zhí)行履植。Executor接口有個抽象實現(xiàn)BaseExecutor類计雌,其中定義了一些模板方法,由子類實現(xiàn)玫霎。
Executor 接口中定義的方法如下:
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
// 更新
int update(MappedStatement ms, Object parameter) throws SQLException;
// 先查詢緩存凿滤,在查詢數(shù)據(jù)庫
<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;
// 返回游標(biāo)對象
<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
// 釋放Statement
List<BatchResult> flushStatements() throws SQLException;
// 事務(wù)提交
void commit(boolean required) throws SQLException;
// 事務(wù)回滾
void rollback(boolean required) throws SQLException;
// 創(chuàng)建緩存的鍵值對
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);
// 獲取事務(wù)對象
Transaction getTransaction();
void close(boolean forceRollback);
boolean isClosed();
void setExecutorWrapper(Executor executor);
}
1.1 Executor繼承關(guān)系
Executor接口有兩個實現(xiàn),一個是BaseExecutor抽象類庶近,一個是CachingExecutor實現(xiàn)類鸭巴。BaseExecutor抽象類有四個實現(xiàn)SimpleExecutor,BathExecutor, ReuseExecutor, ClosedExecutor拦盹。
BaseExecutor抽象類 采用模版方法的設(shè)計模式,定義了一些模版方法溪椎,即抽象方法普舆。Executor接口的其它方法BaseExecutor都給出了默認(rèn)實現(xiàn),進行緩存管理和事務(wù)操作校读,從而降低了接口的實現(xiàn)的難度沼侣。
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;
四類Executor中,默認(rèn)采用的是SimpleExectutor歉秫。每個Executor的特點如下:
- SimpleExecutor:默認(rèn)的執(zhí)行器蛾洛,每次執(zhí)行update或者select操作,都會創(chuàng)建一個Statement對象,執(zhí)行結(jié)束后關(guān)閉Statement對象轧膘。
- ReuseExecutor:可重用執(zhí)行器钞螟,重用的是Statement對象,第一次執(zhí)行一條sql谎碍,會將這條sql的Statement對象緩存在key-value結(jié)構(gòu)的map緩存中鳞滨。下一次執(zhí)行,就可以從緩存中取出Statement對象蟆淀,減少了重復(fù)編譯的次數(shù)拯啦,從而提高了性能。每個SqlSession對象都有一個Executor對象熔任,因此這個緩存是SqlSession級別的褒链,當(dāng)SqlSession銷毀時,緩存也會銷毀疑苔。
- BatchExecutor:批量執(zhí)行器甫匹,默認(rèn)情況是每次執(zhí)行一條sql,MyBatis都會發(fā)送一條sql夯巷。而批量執(zhí)行器的操作是赛惩,每次執(zhí)行一條sql,不會立馬發(fā)送到數(shù)據(jù)庫趁餐,而是批量一次性發(fā)送sql喷兼。
4.ClosedExecutor: ResultLoaderMap的內(nèi)部類,用來進行處理懶加載相關(guān)后雷,懶加載相關(guān)將在其它文章中展開介紹季惯。
1.2 Executor 創(chuàng)建
Executor 是怎么創(chuàng)建出來的呢,在前幾篇文章中臀突,我們都沒有顯示的引用Executor勉抓,其實SqlSession引用著Executor對象汉形。在我們從SqlSessionFactory獲取SqlSession對象的時候澎羞,會調(diào)用到SqlSessionFactory的openSession()方法,其實調(diào)用了openSessionFromDataSource方法圈驼,在這個方法中梳码,創(chuàng)建了事物隐圾,也創(chuàng)建了Executor對象。
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);
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();
}
}
順著configuration.newExecutor(tx, execType)掰茶,我們繼續(xù)往里看暇藏。根據(jù)ExecutorType來創(chuàng)建不同類型的執(zhí)行器,默認(rèn)創(chuàng)建的是SimpleExecutor這個執(zhí)行器濒蒋。如果一級緩存開啟(默認(rèn)是開啟的)盐碱,還會用CachingExecutor來包裝SimpleExecutor執(zhí)行器,在這里用到了裝飾者設(shè)計模式。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
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);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 加載插件鏈
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
三種Executor的創(chuàng)建都是調(diào)用了BaseExecutor的構(gòu)造函數(shù)瓮顽,BaseExecutor的構(gòu)造函數(shù)如下县好,涉及到事務(wù)讀喜慶,懶加載趣倾,緩存的相關(guān)初始化聘惦。至此一個SqlSession就有了自己唯一的Executor對象了。
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
1.3 Executor 執(zhí)行
我們調(diào)用mapper接口的方法時儒恋,最終都會調(diào)用到SqlSession的方法善绎,以DefaultSqlSession為例子,查詢方法如下诫尽,最終調(diào)用也就是Executor.query()方法禀酱。Executor是SqlSession的傀儡無疑了。
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
2. SimpleExecutor
SimpleExecutor 是默認(rèn)的執(zhí)行器牧嫉,也是最簡單的執(zhí)行器剂跟。它實現(xiàn)了BaseExecutor定義的四個抽象方法,doUpdate酣藻,doQuery曹洽,doQueryCursor和doFlushStatements四個方法。
在這里以doUpdate為例辽剧,介紹下SimpleExecutor的操作步驟
- 創(chuàng)建StatementHandler
- 創(chuàng)建Statement
- 執(zhí)行sql操作
- 關(guān)閉Statement
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 1. 創(chuàng)建StatementHandler
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// 2. 創(chuàng)建Statement
stmt = prepareStatement(handler, ms.getStatementLog());
// 3. 執(zhí)行sql操作
return handler.update(stmt);
} finally {
// 2. 關(guān)閉Statement
closeStatement(stmt);
}
}
StatementHandler 是用來管理JDBC中的Statement對象送淆,并進行和數(shù)據(jù)庫的操作。StatementHandler和Statement的相關(guān)在其它文章中展開詳述怕轿,在此不贅述偷崩。
3. ReuseExecutor
上面提到ReuseExecutor就是重用Statement對象,如果在一個SqlSession中多次執(zhí)行一條sql撞羽,如果每次都去生成Statement對象阐斜,會造成一大筆資源浪費。因此ReuseExecutor在SimpleExecutor的基礎(chǔ)上诀紊,對prepareStatement()方法進行了改進谒出,將Statement對象緩存在內(nèi)存中,并且免去了第四步:關(guān)閉Statement對象邻奠。
@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);
}
prepareStatement 做的就是四步驟:
- Sql是否命中緩存
- 命中緩存直接從緩存中獲取到Statement對象
- 如果緩存中沒有到推,則創(chuàng)建新的Statement對象
- 接第3步,以sql為key, Statement為value放到緩存中
private final Map<String, Statement> statementMap = new HashMap<>();
/**
* 獲取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();
// 1惕澎。 sql中是否存在緩存中
if (hasStatementFor(sql)) {
// 2. 命中緩存直接從緩存中獲取到
stmt = getStatement(sql);
applyTransactionTimeout(stmt);
} else {
Connection connection = getConnection(statementLog);
// 3. 創(chuàng)建新的Statement對象
stmt = handler.prepare(connection, transaction.getTimeout());
// 4. 以sql為key, Statement為value放到緩存中
putStatement(sql, stmt);
}
handler.parameterize(stmt);
return stmt;
}
/**
* 緩存map中是否存在sql的Statement對象
* @param sql
* @return
*/
private boolean hasStatementFor(String sql) {
try {
Statement statement = statementMap.get(sql);
return statement != null && !statement.getConnection().isClosed();
} catch (SQLException e) {
return false;
}
}
/**
* 從緩存中獲取
* @param s
* @return
*/
private Statement getStatement(String s) {
return statementMap.get(s);
}
/**
* 以sql為key, Statement為value放到緩存中
* @param sql
* @param stmt
*/
private void putStatement(String sql, Statement stmt) {
statementMap.put(sql, stmt);
}
由于SqlSession都引用著自己的Executor對象,因此這個緩存是SqlSession級別的颜骤,如果SqlSession銷毀了唧喉,對應(yīng)的緩存也會將銷毀。
4. BatchExecutor
BatchExecutor 相對于復(fù)雜一些,批量的意思是批量的發(fā)送sql到數(shù)據(jù)庫八孝,而不是一個個的發(fā)送董朝。以doUpdate()為例,先將多個Statement對象存儲到List中干跛,然后再將執(zhí)行結(jié)果放到一個List中子姜。實際原理使用的是JDBC的批處理操作,在執(zhí)行doUpdate方法時楼入,sql不會立馬執(zhí)行哥捕,而是等到commit或者rollback,執(zhí)行JDBC的executeBatch方法嘉熊。
- doUpdate()返回的值是固定的遥赚,不是影響的行數(shù)
- 如果連續(xù)提交相同的sql,則只會執(zhí)行一次
- 提交sql不會立馬執(zhí)行阐肤,而是等到commit時候統(tǒng)一執(zhí)行
- 底層使用的是JDBC的批處理操作凫佛,addBatch()和executeBatch()操作。
// 批量更新處理的固定返回值孕惜,不是影響的行數(shù)
public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;
// Statement集合
private final List<Statement> statementList = new ArrayList<>();
// 批量結(jié)果集合
private final List<BatchResult> batchResultList = new ArrayList<>();
// 上一次Sql語句
private String currentSql;
// 上一次的MappedStatement對象
private MappedStatement currentStatement;
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;
// 1. currentSql和currentStatement初始化為null愧薛,走else分支
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 {
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt); // fix Issues 322
// 2. 設(shè)置currentSql和currentStatement為當(dāng)前sql
currentSql = sql;
currentStatement = ms;
// 3. 將Statement加到list集合中。
statementList.add(stmt);
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
// 4. 調(diào)用JDBC的addBatch()方法衫画,添加到批處理中
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
在doFlushStatements中毫炉,會遍歷statementList集合中的Statement,一條條執(zhí)行碧磅,并將結(jié)果加載到結(jié)果集合中碘箍。
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
try {
List<BatchResult> results = new ArrayList<>();
// 1. 回滾則直接返回
if (isRollback) {
return Collections.emptyList();
}
// 2. 遍歷statementList集合
for (int i = 0, n = statementList.size(); i < n; i++) {
Statement stmt = statementList.get(i);
applyTransactionTimeout(stmt);
BatchResult batchResult = batchResultList.get(i);
try {
// 3. 執(zhí)行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())) { //issue #141
for (Object parameter : parameterObjects) {
keyGenerator.processAfter(this, ms, stmt, parameter);
}
}
// Close statement to close cursor #1109
// 4. 關(guān)閉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);
}
// 5. 將結(jié)果加載到結(jié)果集合中
results.add(batchResult);
}
return results;
} finally {
for (Statement stmt : statementList) {
closeStatement(stmt);
}
currentSql = null;
statementList.clear();
batchResultList.clear();
}
}
5. CachingExecutor
在前面介紹創(chuàng)建Executor對象的時候,會判斷是否開啟了一級緩存鲸郊,如果開啟了丰榴,則用CachingExecutor來包裝以上三種類型中的一種執(zhí)行器,使用裝飾者設(shè)計模式來增強執(zhí)行器的緩存功能秆撮。
// 是否開啟了一級緩存四濒,默認(rèn)開啟
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
CachingExecutor 的構(gòu)造函數(shù)如下
// 1. 委托執(zhí)行器,也就是被包裝的三種執(zhí)行器的中的一種
private final Executor delegate;
// 2. 緩存管理類职辨,用來管理TransactionalCache
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
public CachingExecutor(Executor delegate) {
// 3. 互相引用
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
在執(zhí)行更新操作的時候盗蟆,先清空緩存,再去執(zhí)行實際執(zhí)行器的update方法
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
// 清空緩存
flushCacheIfRequired(ms);
// 調(diào)用實際執(zhí)行器的update方法
return delegate.update(ms, parameterObject);
}
在執(zhí)行查詢的時候舒裤,先從緩存中獲取喳资,如果緩存中沒有再去調(diào)用實際執(zhí)行器的query方法查詢數(shù)據(jù)庫,并放到緩存中返回腾供。
- 獲取緩存key
- 查詢緩存仆邓,如果緩存命中直接返回
- 如果緩存中沒有鲜滩,或者緩存不存在,則查詢數(shù)據(jù)庫节值,并放到緩存中
TransactionalCacheManager 和 TransactionalCache涉及到緩存模塊徙硅,也打算在其它文章中講解,在此也就一筆帶過搞疗。
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 1. 獲取緩存key
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) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
// 2. 如果緩存為空嗓蘑,則查詢數(shù)據(jù)庫放到緩存中
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
6. Executor 栗子
前面講解了四種Executor,實際進行操作的是SimpleExecutor匿乃,ReuseExecutor桩皿,BatchExecutor三種,如果不指定執(zhí)行器類型扳埂,默認(rèn)是SimpleExecutor业簿。如果開啟了緩存,則會使用CachingExecutor進行包裝阳懂,增加緩存邏輯梅尤。
接下來將用幾個栗子來實際操作一番,也能將三種執(zhí)行器的特點展現(xiàn)出來岩调。
6.1 SimpleExecutor
在獲取sqlSession時可以指定執(zhí)行器的類型巷燥,先看看SimpleExecutor執(zhí)行結(jié)果。
public static void main(String[] args) {
try {
// 1. 讀取配置
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 創(chuàng)建SqlSessionFactory工廠
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 獲取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE);
// 4. 獲取Mapper
TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
// 5. 執(zhí)行接口方法
for(long i = 1; i < 3; i++){
TTestUser userInfo = userMapper.selectByPrimaryKey(i);
System.out.println("userInfo = " + JSONUtil.toJsonStr(userInfo));
}
// 6. 提交事物
sqlSession.commit();
// 7. 關(guān)閉資源
sqlSession.close();
inputStream.close();
} catch (Exception e){
log.error(e.getMessage(), e);
}
}
可以從日志打印看到我們執(zhí)行了2次sql号枕,Statement編譯了2次缰揪。
6.2 ReuseExecutor
ReuseExecutor 重用的就是Statement對象,以key-value形式來緩存Statement對象葱淳,避免了同一個sql編譯多次钝腺,從而提高性能。
public static void main(String[] args) {
try {
// 1. 讀取配置
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 創(chuàng)建SqlSessionFactory工廠
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 獲取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE);
// 4. 獲取Mapper
TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
// 5. 執(zhí)行接口方法
for(long i = 1; i < 3; i++){
TTestUser userInfo = userMapper.selectByPrimaryKey(i);
System.out.println("userInfo = " + JSONUtil.toJsonStr(userInfo));
}
// 6. 提交事物
sqlSession.commit();
// 7. 關(guān)閉資源
sqlSession.close();
inputStream.close();
} catch (Exception e){
log.error(e.getMessage(), e);
}
}
可以從日志打印看到我們執(zhí)行了2次sql赞厕,Statement編譯了1次艳狐。
6.3 BatchExecutor
BatchExecutor 就是在commit時一次性提交sql,而不是發(fā)送一次皿桑,執(zhí)行一次毫目。
try {
// 1. 讀取配置
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 創(chuàng)建SqlSessionFactory工廠
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 獲取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
// 4. 獲取Mapper
TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
// 5. 執(zhí)行接口方法
for(long i = 1; i < 3; i++){
TTestUser userInfo = new TTestUser();
userInfo.setMemberId(2000+i);
userInfo.setNickname(2000+i+"_nick");
userInfo.setRealName(2000+i+"_real");
userMapper.insertSelective(userInfo);
// 模擬插入間隔
Thread.sleep(1000);
}
System.out.println("-------開始提交事務(wù)--------- ");
// 6. 提交事物
sqlSession.commit();
System.out.println("-------結(jié)束提交事務(wù)--------- ");
// 7. 關(guān)閉資源
sqlSession.close();
inputStream.close();
} catch (Exception e){
log.error(e.getMessage(), e);
}
這個例子不太明顯,但是可以看到雖然 Thread.sleep(1000)了诲侮,但是BatchExecutor中的操作是在最后commit時镀虐,才會插入數(shù)據(jù)到數(shù)據(jù)庫中去,插入時間是一致的沟绪。
6.4 CachingExecutor
CachingExecutor不會執(zhí)行具體的更新和查詢操作刮便,而是在執(zhí)行更新操作的時候先清除下緩存,在執(zhí)行查詢操作的時候先從緩存中查找绽慈,如果命中緩存直接返回诺核。
try {
// 1. 讀取配置
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 創(chuàng)建SqlSessionFactory工廠
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 獲取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE);
// 4. 獲取Mapper
TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
// 5. 執(zhí)行接口方法
for(long i = 1; i < 3; i++){
TTestUser userInfo = userMapper.selectByPrimaryKey(2001L);
System.out.println("userInfo = " + JSONUtil.toJsonStr(userInfo));
}
// 6. 提交事物
sqlSession.commit();
// 7. 關(guān)閉資源
sqlSession.close();
inputStream.close();
} catch (Exception e){
log.error(e.getMessage(), e);
}
在這個栗子中抄肖,雖然我們執(zhí)行了兩次sql,但是參數(shù)和sql語句都是一樣的窖杀,所以一次的查詢結(jié)果會被緩存,第二次查詢的時候直接從緩存中去取裙士。
7. 總結(jié)
這篇文章介紹了Executor執(zhí)行器的接口入客,繼承關(guān)系,三種執(zhí)行器腿椎,以及用了幾個例子來介紹他們的區(qū)別桌硫,想必大家對Executor已經(jīng)有了很全面的了解了吧。希望大家能夠在評論區(qū)評論指正啃炸,一起進步铆隘。