SqlSession
SqlSession提供執行Sql命令,獲取Mapper以及事務管理的接口。
內部有selectOne,selectList,selectMap,insert,update,commit,rollback,getConnection
等方法。
selectList
作爲select
操作的最主要的實現,selectOne
和selectMap
都是通過對selectList
的結果進行處理得到。
例如selectOne
@Override
public <T> T selectOne(String statement) {
return this.selectOne(statement, null);
}
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
selectMap
@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
return this.selectMap(statement, parameter, mapKey, RowBounds.DEFAULT);
}
@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
final List<? extends V> list = selectList(statement, parameter, rowBounds);
final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<>(mapKey,
configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
final DefaultResultContext<V> context = new DefaultResultContext<>();
for (V o : list) {
context.nextResultObject(o);
mapResultHandler.handleResult(context);
}
return mapResultHandler.getMappedResults();
}
selectList
通過Executor執行查詢操作
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
query
以BaseExecutor爲例,首先從localCache中獲取結果protected PerpetualCache localCache;
這個cache是基於HashMap實現的。如果緩存中存在就直接返回結果,否則調用queryFromDatabase()
從數據庫中查詢,此方法請看下面。在完成結果的查詢後,會根據緩存的範圍LocalCacheScope
判斷是否需要清空緩存,如果是STATEMENT級別,則會清空緩存。
@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;
}
queryFromDatabase
委託給doQuery
實現查詢。在查詢完成後會保存到localCache
中
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
doQuery
看到這裏就已經很熟悉了,出現了JDBC編程中常見的Statement
接口和數據庫連接Connection
。看到有個StatementHandler
,這裏newStatementHandler()
返回的是RoutingStatementHandler
。
RoutingStatementHandler
本質上是代理類,根據StatementType
這個枚舉類型來返回真正處理的Handler
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException {
Statement stmt = null;
try {
flushStatements();
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
我們選到PreparedStatementHandler
的query
就是純JDBC的操作了。
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
update
在mybatis中insert
和delete
是基於update
實現的
@Override
public int insert(String statement) {
return insert(statement, null);
}
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int delete(String statement) {
return update(statement, null);
}
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
來到BaseExecutor關於update的實現,會發現在更新之前會清空緩存
@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);
}
一級緩存
一級緩存也叫本地緩存,MyBatis 的一級緩存是在會話(SqlSession)層面進行緩存的。MyBatis 的一級緩存是默認開啓的,不需要任何的配置。首先我們必須去弄清楚一個問題,在MyBatis 執行的流程裏面,涉及到這麼多的對象,那麼緩存PerpetualCache 應該放在哪個對象裏面去維護?如果要在同一個會話裏面共享一級緩存,這個對象肯定是在SqlSession 裏面創建的,作爲SqlSession 的一個屬性。
DefaultSqlSession 裏面只有兩個屬性,Configuration 是全局的,所以緩存只可能放在Executor 裏面維護——SimpleExecutor/ReuseExecutor/BatchExecutor 的父類BaseExecutor 的構造函數中持有了PerpetualCache。在同一個會話裏面,多次執行相同的SQL 語句,會直接從內存取到緩存的結果,不會再發送SQL 到數據庫。但是不同的會話裏面,即使執行的SQL 一模一樣(通過一個Mapper 的同一個方法的相同參數調用),也不能使用到一級緩存。
每當我們使用MyBatis開啓一次和數據庫的會話,MyBatis會創建出一個SqlSession對象表示一次數據庫會話。
在對數據庫的一次會話中,我們有可能會反覆地執行完全相同的查詢語句,如果不採取一些措施的話,每一次查詢都會查詢一次數據庫,而我們在極短的時間內做了完全相同的查詢,那麼它們的結果極有可能完全相同,由於查詢一次數據庫的代價很大,這有可能造成很大的資源浪費。
爲了解決這一問題,減少資源的浪費,MyBatis會在表示會話的SqlSession對象中建立一個簡單的緩存,將每次查詢到的結果結果緩存起來,當下次查詢的時候,如果判斷先前有個完全一樣的查詢,會直接從緩存中直接將結果取出,返回給用戶,不需要再進行一次數據庫查詢了。
二級緩存
一級緩存只是對於一個Session有效,那麼二級緩存爲了使更大範圍有效採用CachingExecutor
和在MappedStatement
中設置緩存來實現mapper範圍內的緩存。如何利用到這個二級緩存呢?如下分析select
的過程
CachingExecutor的query()
會發現它會從MappedStatement中獲取cache。如果存在對應的結果就直接返回,否則調用BaseExecutor的query進行查詢。因此,在開啓Mybatis二級緩存的情況下,查詢過程是MappedStatement中Cache
-> BaseExecutor的localCache
-> queryFromDatabase()
。
@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); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
在update的情況下會清空緩存
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}
}
MappedStatement
對應與具體在Mapper.xml
中寫的方法,如下圖
這個MappedStatement
中屬性id
就是在這裏被賦值的。而Configuration
中有個mappedStatements
Map就是存放各種方法。Key爲id。