mybatis的查詢使用的sqlSession類主要爲:DefaultSqlSession.java,在這個類裏提供了selectOne,selectList,insert,update,delete,select,selectMap之類的dml通用方法以及commit、rollback這類的事務控制方法。java
這裏今天主要講述dml方法涉及到對應的事務內緩存,首先咱們先看看selectOne方法sql
public <T> T selectOne(String statement) { return this.<T>selectOne(statement, null); } 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.<T>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; } }
從上能夠看出selectOne最終都是調用的selectList,接下來看selectList數據庫
public <E> List<E> selectList(String statement) { return this.selectList(statement, null); } public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); } public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); return result; } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
selectList方法最終是要調用到 selectList(String statement, Object parameter, RowBounds rowBounds) 方法。在這裏它會調用 org.apache.ibatis.executor.Executor 類的實現類的 query方法進行查詢相關的後續邏輯。這裏先看看對應query方法apache
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } 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, parameterObject, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks } return list; } } return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
在這裏能夠看到會根據sql、sql對應的參數建立一個CacheKey。第一個query方法會調用第二個,而後先會從MappedStatement中獲取cache,這裏先不關注這個,由於只有設置了useCache的時候纔有可能會有cache,而本次討論查詢緩存不是這個cache。接下來就要調用BaseExecutor.query方法緩存
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(); } deferredLoads.clear(); // issue #601 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { clearLocalCache(); // issue #482 } } return list; }
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;mybatis
上面這一行是從localcache中獲取事務中的查詢緩存,若是有就返回對應的緩存,沒有則會去數據庫裏查詢,這時就進入了queryFromDatabase方法了app
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; }
從這裏能夠看出會將查詢結果放入localCache中,並返回結果。ui
從上面能夠看出,若是兩次調用同一條查詢語句而且查詢條件一致,那麼就只會查詢一次,第二次會從localCache中獲取。那麼若是第一次查詢後有insert、update、delete、操做時,第二次相同查詢還會從localCache中獲取嗎?this
咱們再來看看DefaultSqlSession類中的insert、update、delete方法code
public int insert(String statement) { return insert(statement, null); } public int insert(String statement, Object parameter) { return update(statement, parameter); } public int update(String statement) { return update(statement, null); } 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(); } } public int delete(String statement) { return update(statement, null); } public int delete(String statement, Object parameter) { return update(statement, parameter); }
能夠看出來最終都是調用的 update(String statement, Object parameter) 方法,這樣很好,咱們就只要關注這個方法的執行了。從configuration中獲取對應mybatis以及咱們本身要調用的dml方法的相關配置後,就會執行executor.update 方法。如今,咱們進入這個方法看看
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); }
從這能夠看出來這裏會調用clearLocalCache()方法後,再執行doUpdate方法去更新數據。咱們來看看clearLocalCache()方法到底幹了些什麼?
public void clearLocalCache() { if (!closed) { localCache.clear(); localOutputParameterCache.clear(); } }
很簡單,就是清理localCache中的緩存。也就是即便咱們在同一個事務中使用了兩次徹底相同的查詢,若是在這兩次查詢中有update、delete、insert操做,那麼第二次查詢會直接查詢數據庫,而不會從localCache中獲取,而若是沒有,那麼就會從localCache中獲取。由於這個緣由,咱們須要在寫代碼時注意到,在第一次查詢後獲得結果集,若是結果集這時候咱們人爲修改了,那麼就會在第二次查詢時會獲取到咱們修改後的結果集,而不是正確的數據。