- 一级缓存是sqlsession级别的,不同sqlsession之间的缓存互不影响,而二级缓存实现了不同sqlsession之间的缓存共享。
- 二级缓存是mapper级别的,同一个mapper的namespace相同,所以同一个mapper(或者namespace)下的查询共享一个二级缓存,不同mapper之间的二级缓存互不影响。
- 如果配置了二级缓存,首先从二级缓存中查询,如果未找到再从一级缓存中查询。
二级缓存的配置方式
- mybatis的配置文件中开启二级缓存
1 | <setting name="cacheEnabled" value="true"/> |
- 在Mapper.xml中设置
1 | <mapper namespace="mapper.StudentMapper"> |
- 假如某个语句不想使用缓存,每次都需要从数据库查询最新的数据,那么通过useCache设置即可.
1 | <mapper namespace="mapper.StudentMapper"> |
- 刷新缓存,同一个namespace下,insert、update、delete操作默认会刷新缓存,当然也可以通过设置flushCache=false不刷新缓存.
1 | <mapper namespace="mapper.StudentMapper"> |
Executor的生成
二级缓存使用CachingExecutor实现,看一下Executor的生成过程。
1.SqlSessionFactory
从SqlSessionFactoryBuilder说起,通过它的build方法构建SqlSessionFactory时返回一个DefaultSqlSessionFactory:
1 | public SqlSessionFactory build(Configuration config) { |
然后进入DefaultSqlSessionFactory,在它的openSeesion方法中又调用了openSessionFromDataSource方法,在此方法中,通过调用configuration的newExecutor方法创建了Executor:
1 | private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { |
2. Configuration
Configuration的newExecutor方法中,根据Executor类型判断创建哪种执行器,默认是使用SimpleExecutor,如果开启了二级缓存,使用CachingExecutor对执行器进行装饰,完成Executor的创建:
1 | public Executor newExecutor(Transaction transaction, ExecutorType executorType) { |
缓存的使用过程
1. DefaultSqlSessionFactory
回到DefaultSqlSessionFactory的penSessionFromDataSource方法,该方法返回了一个DefaultSqlSession,那么进入DefaultSqlSession,以selectList方法为例,调用了Executor的query方法,由于开启二级缓存使用的是CachingExecutor,那么进入CachingExecutor查看源码:
1 |
|
2. CachingExecutor
CachingExecutor包含了两个成员变量分别为Executor类型的delegate和TransactionalCacheManager类型的tcm。
从CachingExecutor的构造函数中也可以看到,CachingExecutor使用的是装饰者模式,对基本的Executor实现类进行装饰。
1 | public class CachingExecutor implements Executor { |
CachingExecutor的query方法中,由于CachingExecutor使用了装饰者模式对Executor实现者(delegate变量)进行了装饰,如果未开启二级缓存,将调用被装饰者delegate的query方法进行查询,接下来流程就与一级缓存的流程一致。如果开启了二级缓存,首先从缓存中获取数据,如果未获取到,同样调用delegate的query方法走一级缓存的流程,然后将结果放入到二级缓存中:
1 |
|
3. Cache
首先,在query方法有一个从MappedStatement获取Cache对象的过程,MappedStatement是在MyBatis启动的时候存入Configuration中的,因此Mybatis的二级缓存的生命周期与应用的生命周期一致,应用不结束,二级缓存就会一直存在。
1 | //从MappedStatement获取缓存 |
Cache只是一个接口,它有多个实现类,并且使用了装饰者模式进行层层包装,在构建Cache的过程中会提到。
1 | public interface Cache { |
4. TransactionalCacheManager
从tcm缓存中获取数据的过程:
1 | List<E> list = (List<E>) tcm.getObject(cache, key);//从缓存取数据 |
TransactionalCacheManager中使用了HashMap存储结构,key为Cache对象,value为TransactionalCache:
1 | public class TransactionalCacheManager { |
key:Cache对象,是一个接口,它会根据Mapper文件中配置的
比如使用Mybatis内置的cache:
1 | <cache eviction="FIFO" flushInterval="10000" readOnly="true" /> |
value:TransactionalCache事物缓存,它也是Cache的一个实现类。
调用TransactionalCacheManager的getObject或者putObject方法时都会首先调用getTransactionalCache方法获取当前的Cache对应的TransactionalCache对象,如果没有将会实例化一个TransactionalCache对象,因为TransactionalCache是Cache的实现类,因此可以使用TransactionalCache继续装饰当前的Cache:
1 | public class TransactionalCacheManager { |
5. TransactionalCache
存取流程:
当调用tcm.getObject时,实际上调用的就是TransactionalCache类的getObject()方法,由于TransactionalCache装饰了当前的Cache对象,索引调用getObject()职责会一层一层传递给PerpetualCache,然后获取缓存数据。TransactionalCache类还有一个clearOnCommit成员变量,表示使用事务提交时是否清空缓存,默认为false。
tcm.putObject()方法用于将数据放入缓存,实际上也是调用的TransactionalCache的putObject()方法,然后将数据放入到entriesToAddOnCommit待提交缓存数据中。
1 | public class TransactionalCache implements Cache { |
将数据放入缓存时首先将缓存放入到了待提交缓存中,所以如果不使用commit提交事务的时候,两次相同的查询,第二次并不会拿到第一次的缓存数据,因为第一次的数据被放在待提交的缓存数据中,相当于没有提交缓存数据,因此查询的时候也就取不到。
接下来就来看看DefaultSqlSession的commit方法,它调用了Executor实现类的commit方法:
1 |
|
CachingExecutor的commit方法:
1 |
|
TransactionalCacheManager的commit方法:1
2
3
4
5public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();//调用了TransactionalCache的commit方法
}
}
回到TransactionalCache类,查看它的commit方法:
1 |
|
TransactionalCache的commit方法中,如果clearOnCommit为true,那么将会清空缓存数据,如果为false,本次需要提交的缓存数据将会委托给TransactionalCache装饰的Cache类,调用putObject方法,将数据加入到缓存中。
Cache的构建过程
1.XmlMapperBuilder
XmlMapperBuilder中的cacheElement方法首先或获取缓存的配置信息,然后根据配置信息调用MapperBuilderAssistant的useNewCache方法创建缓存对象。
1 | private void cacheElement(XNode context) throws Exception { |
2.MapperBuilderAssistant
MapperBuilderAssistant中的useNewCache方法中使用建造者模式创建了Cache对象
1 | public Cache useNewCache(Class<? extends Cache> typeClass, |
3.CacheBuilder
CacheBuilder的build方法中,首先调用了setDefaultImplementations()将PerpetualCache作为默认的实现类,完成Cache的实例化。接着会判断当前创建的实例是否是PerpetualCache的实例,如果是调用setStandardDecorators方法对PerpetualCache进行装饰。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public Cache build() {
setDefaultImplementations();//设置默认的Cache实现类
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);
// issue #352, do not apply decorators to custom caches
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) {
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
cache = setStandardDecorators(cache);//设置标准的装饰器
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}
return cache;
}
private void setDefaultImplementations() {
if (implementation == null) {
implementation = PerpetualCache.class;//使用PerpetualCache作为默认的实现类
if (decorators.isEmpty()) {
decorators.add(LruCache.class);//如果装饰器为空,添加LruCache装饰器,对Cache对象进行装饰
}
}
}
在setStandardDecorators中可以看到装饰链:
BlockingCache -> SynchronizedCache -> LoggingCache -> SerializedCache -> ScheduledCache-> LruCache -> PerpetualCache。
其中LruCache是在setDefaultImplementations()中设置的。
通过装饰链可以明白为什么在TransactionalCache添加或者获取缓存的时候最终会从PerpetualCache调用。
一级缓存也是使用PerpetualCache实现,二级缓存实际上就是通过装饰者模式对PerpetualCache进行层层包装,所以从二级缓存中未获取到数据时,还可以走一级缓存的流程。
1 | private Cache setStandardDecorators(Cache cache) { |
参考:
五月的仓颉:【MyBatis源码解析】MyBatis一二级缓存
Double-Eggs:【Mybatis框架】查询缓存(二级缓存)
零度anngle:MyBatis缓存策略之二级缓存
jtlgb:mybatis 缓存(cache)的使用