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中获取。由于这个缘由,咱们须要在写代码时注意到,在第一次查询后获得结果集,若是结果集这时候咱们人为修改了,那么就会在第二次查询时会获取到咱们修改后的结果集,而不是正确的数据。