Mybatis的缓存氛围二级缓存和一级缓存,其中一级缓存默认开启,二级缓存默认不开启。
一级缓存的具体实现在了BaseExecutor类里面。
在执行一条查询语句的时候,第一次设计到一级缓存的时候是在BaseExecutor的query()方法里面,这里为这次查询生成了相应的key用来在缓存中定位。
@Override
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
在这里,调用了createCacheKey()方法生成了缓存的key。
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
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.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
其中CacheKey的update()方法主要是根据传入的对象的hashcode,更新这个key的hashcode,可以看到这里的缓存key的生成涉及到了mapperStatement的id,具体的sql,sql的分页情况以及涉及到的参数和配置环境的id,根据上述成员完成对于单次查询唯一性的确定,保证之后缓存结果的获取可以准确无误。
在完成cacheKey的获取之后,将会在query()具体对一级缓存进行操作。
@SuppressWarnings("unchecked")
@Override
public List 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 list;
try {
queryStack++;
list = resultHandler == null ? (List) 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;
}
在这里可以看到,如果能够在localCache中通过CacheKey得到相应的结果,那么直接会从这里的缓存得到结果并返回,反之,则将正式执行查询的sql语句。一级缓存就在这里发挥了作用。
这里可以看到,一级缓存的容器实则是BaseExecutor的map,而可以看到DefaultSqlSessionFactory生成session的方法。
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();
}
}
每一个session是与一个executor对应的,那么由此可见一级缓存是不同的session之间隔离的。
然而,一级缓存会随着每一次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);
}
可见BaseExecutor中的update()方法,每次执行前都会清空本地的缓存,所以一级缓存会随着更新操作而清楚。
二级缓存默认不开启,二级缓存的开启需要在配置中显示的配置CacheEnable参数,当这个参数配置为true的时候,在创建新的executor的时候,会对executor加上一层包装类CachingExecutor达到实现二级缓存的目的。
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;
}
二级缓存发生的地方也就是在于CachingExecutor的query()方法里面。
@Override
public List 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 list = (List) 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);
}
用这里可以看到,这里的缓存的获取是针对一个MappedStatement的,而一个MappedStatement所对应的一般是一条sql语句,也就是说,二级缓存对应的是一条sql语句,再根据生成的CacheKey在二级缓存中寻找结果,并且可以看到,开启了二级缓存之后,只有二级缓存找不到才会接续下一层的query()方法的调用,由此可见,二级缓存的优先级高于一级缓存。
如果开启了二级缓存没有查到,那么在经过查到结果之后,会调用putObject()方法来提交结果。
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
这里可以看到,这里根据这个MappedStatement的cache更新的缓存只是还是针对单个session的,此时,并没有保证整个MappedStatement的共享。只有在调用了commit()方法才会保证二级缓存在同一个sql下面的共享。
private void flushPendingEntries() {
for (Map.Entry