一、MyBatis中有几级缓存?
有两级缓存,分别是一级缓存和二级缓存
二、什么是一级缓存?什么是二级缓存?
一级缓存

一级缓存失效情况
- sqlsession变了,缓存失效 (不同的session进行查询,缓存失效)
- sqlsession不变,查询条件不同、中间发生了增删改操作、手动清除缓存三种情况下,一级缓存失效
一级缓存也叫本地缓存,是sqlsession级别的缓存,默认是打开的且不能被关闭。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的
使用SqlSession进行第一次查询时,MyBatis会根据执行的方法和参数,通过一定的算法生成缓存的键值,然后将键值和查询后的结果保存在Map对象中
如果第二次查询,执行的是相同的方法和参数,那么MyBatis就会直接从缓存中这个map中读取。
@Override
public <E> List<E> 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);
}
@SuppressWarnings("unchecked")
@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++;
// 如果resultHandler不存在, 将从缓存中获取结果集
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();
// 如果缓存是STATEMENT级别的,清楚缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
二级缓存

二级缓存是 SqlSessionFactory级别的缓存,是与 namespace 进行绑定的。
MyBatis的二级缓存实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,
通过Cache接口实现类不同的组合,对Cache的可控性也更强。
MyBatis在多表查询时,极大可能会出现脏数据,这导致安全使用二级缓存的条件比较苛刻,在分布式环境下,建议使用 Redis。
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);
// 根据事务对象和execType对象创建对应的执行器对象Executor
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();
}
}
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);
}
// 如果开启了二级缓存, 那么使用wrapper模式包装已经创建的执行器
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 这里是从MappedStatement中获取的缓存,所以是二级缓存
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);
}
三、如何启用一级缓存和二级缓存
1)启用一级缓存
一级缓存默认是开启的
2)启用二级缓存
两个步骤
S1:在mybatis.config.xml文件中的 <settings>
元素中配置 cacheEnabled
参数,默认值:true,设置为false,表示关闭二级缓存
<configuration>
<properties>
......
</properties>
<settings>
<!--是否开启二级缓存, 默认是开启的-->
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
S2:在xxxMapper.xml中添加 <cache />
<cache />
为指定的语句开启或关闭 二级缓存
在xxxMapper.xml文件中的 <select>、<insert>、<update>、<delete>
语句中通过 useCache
和 flushCache
设置