1、目标
本文的主要目标是探究Mybatis源码中的一级缓存和二级缓存,先分析标签作用,然后分析一级缓存和二级缓存的源码
2、一级缓存的标签
2.1 cacheEnabled标签
cacheEnabled是控制二级缓存是否可以使用,默认值是true表示二级缓存可以使用(不表示开启了二级缓存),但是一级缓存始终存在
cacheEnabled=true:二级缓存可以使用(不表示开启了二级缓存),查询流程是先查询二级缓存的执行器CacheExecutor,接着查询一级缓存的执行器BaseExecutor,如果都没有最后查询数据库
cacheEnabled=false:二级缓存不能使用,一级缓存存在,查询流程是先查询一级缓存的执行器BaseExecutor,如果没有就查询数据库
注意:二级缓存可以使用不表示二级缓存已经开启了
cacheEnabled标签用在mybatis-config.xml文件中,在settings标签内配置这个标签
<settings>
<setting name="cacheEnabled" value="false"/>
</settings>
2.2 localCacheScope标签
localCacheScope是控制一级缓存的作用范围,有效值是SESSION和STATEMENT,默认值是SESSION
localCacheScope=SESSION:一级缓存在同一个SqlSession的执行过程中遇到相同的sql语句会查询一级缓存并返回,当执行update语句时一级缓存会失效
localCacheScope=STATEMENT:一级缓存在执行完成sql语句就会失效
localCacheScope标签用在mybatis-config.xml文件中,在settings标签内配置这个标签
<settings>
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
3、二级缓存的标签
cache标签
cache标签是开启二级缓存,作用范围是一个mapper接口
开启二级缓存必须满足的三个条件:
① cacheEnabled=true
② 在mapper.xml文件中设置cache标签
③ 同时sqlSession必须commit才会将数据存放到二级缓存中并清除一级缓存
cache标签用在mapper.xml文件中,在mapper标签内部
<mapper namespace="org.apache.mybatisDemo.BlogMapper">
<select id="selectBlog" resultType="org.apache.mybatisDemo.Blog">
select * from Blog where author = #{author}
</select>
<cache/>
</mapper>
4、一级缓存的源码分析
MyBatis的一级缓存,使用的是PerpetualCache
二级缓存使用的是TransactionalCache
4.1 BaseExecutor的一级缓存
BaseExecutor执行器包含一级缓存localCache,它是PerpetualCache对象
public class PerpetualCache implements Cache {
private final String id;
private final Map<Object, Object> cache = new HashMap<>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public int getSize() {
return cache.size();
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public boolean equals(Object o) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
}
cache是一个HashMap,还保存了id,equals方法是如果id相同就可以,hashCode方法是返回id的hashCode,其中putObject方法会向map添加元素,getObject方法从map获取元素,removeObject方法删除map的元素,clear方法是清除map的所有元素(size变成0)
4.2 cacheEnabled=false不走CacheExecutor,走BaseExecutor
查询流程:
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog("lisi");
System.out.println(blog);
blog = mapper.selectBlog("lisi");
System.out.println(blog);
}
同一个SqlSession对象查询同一个接口两次,可以看到查询只会走BaseExecutor执行器的一级缓存,第一次查询会查询数据库并保存到一级缓存,第二次查询会查询一级缓存并返回
第一次查询:
cacheEnabled=false,调用selectList方法查询不走CacheExecutor,直接走BaseExecutor,发现一级缓存没有就查询数据库
其中,localCache.getObject(key)方法会调用PerpetualCache对象的cache.get(key)方法,其中cache是一个HashMap
一级缓存不存在数据就查询数据库得到结果list,然后放到BaseExecutor执行器的一级缓存localCache这个map中,key是查询的sql语句,value是查询结果list
其中,localCache.putObject(key, list)方法会调用PerpetualCache对象的cache.put(key, value),其中cache是一个HashMap
将数据库查询结果存放到一级缓存后,会判断localCacheScope如果是STATEMENT就会清除一级缓存,这里localCacheScope是SESSION因此不会清除一级缓存
localCacheScope表示作用范围,如果是STATEMENT表示作用域是查询的sql语句,如果是SESSION表示作用域是同一个SqlSession对象
清除一级缓存会调用BaseExecutor的localCache.clear()方法,它会调用PerpetualCache的cache.clear()方法,cache是一个HashMap
第二次查询:
第二次查询发现BaseExecutor执行器的一级缓存存在数据就直接返回结果,不会查询数据库了,从而实现了同一个SqlSession对象查询多次相同的sql语句返回相同的结果是查询一级缓存的
4.3 cacheEnabled=true走CacheExecutor,走BaseExecutor
查询流程:
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog("lisi");
System.out.println(blog);
blog = mapper.selectBlog("lisi");
System.out.println(blog);
}
同一个SqlSession对象查询同一个接口两次,可以看到查询会先走CacheExecutor的二级缓存,然后走BaseExecutor执行器的一级缓存,如果都没有会查询数据库,第一次查询会查询二级缓存但此时二级缓存cache为空表示没有开启二级缓存,因此会查询一级缓存发现没有就查询数据库并保存到一级缓存,第二次查询会查询二级缓存发现没有然后查询一级缓存发现存在就返回结果
第一次查询:
cacheEnabled=true,调用selectList方法查询会先走CachingExecutor执行器的二级缓存,发现没有开启二级缓存就查询BaseExecutor执行器的一级缓存
查询BaseExecutor执行器的一级缓存发现没有就查询数据库并保存到一级缓存中
其中,localCache.getObject(key)方法会调用PerpetualCache对象的cache.get(key)方法,其中cache是一个HashMap
一级缓存不存在数据就查询数据库得到结果list,然后放到BaseExecutor执行器的一级缓存localCache这个map中,key是查询的sql语句,value是查询结果list
其中,localCache.putObject(key, list)方法会调用PerpetualCache对象的cache.put(key, value),其中cache是一个HashMap
将数据库查询结果存放到一级缓存后,会判断localCacheScope如果是STATEMENT就会清除一级缓存,这里localCacheScope是SESSION因此不会清除一级缓存
localCacheScope表示作用范围,如果是STATEMENT表示作用域是查询的sql语句,如果是SESSION表示作用域是同一个SqlSession对象
清除一级缓存会调用BaseExecutor的localCache.clear()方法,它会调用PerpetualCache的cache.clear()方法,cache是一个HashMap
第二次查询:
第二次查询二级缓存还是没有就查询一级缓存,因为二级缓存没有开启
查询一级缓存发现有就直接返回
第二次查询发现BaseExecutor执行器的一级缓存存在数据就直接返回结果,不会查询数据库了,从而实现了同一个SqlSession对象查询多次相同的sql语句返回相同的结果是查询一级缓存的
4.4 cacheEnabled和localCacheScope的作用
cacheEnabled=true比cacheEnabled=false只是多走了二级缓存的执行器CachingExecutor,但是这两种情况都没有开启二级缓存,因此这两种情况数据还是存储在一级缓存中。
cacheEnabled是控制二级缓存是否可以使用,默认值是true表示二级缓存可以使用(不表示开启了二级缓存),但是一级缓存始终存在
localCacheScope如果是STATEMENT就会清除一级缓存
localCacheScope表示作用范围,如果是STATEMENT表示作用域是查询的sql语句,如果是SESSION表示作用域是同一个SqlSession对象
5、二级缓存的源码分析
开启二级缓存必须满足的三个条件:
① cacheEnabled=true
② 在mapper.xml文件中设置cache标签
③ 同时sqlSession必须commit才会将数据存放到二级缓存中并清除一级缓存
如果没有执行commit方法会将数据保存在一级缓存中,二级缓存没有数据
MyBatis的一级缓存,使用的是PerpetualCache
二级缓存使用的是TransactionalCache
5.1 CachingExecutor的二级缓存
CachingExecutor执行器包含一个TransactionalCacheManager对象,它是二级缓存
public class TransactionalCacheManager {
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
public void clear(Cache cache) {
getTransactionalCache(cache).clear();
}
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
private TransactionalCache getTransactionalCache(Cache cache) {
return MapUtil.computeIfAbsent(transactionalCaches, cache, TransactionalCache::new);
}
}
TransactionalCacheManager是管理TransactionalCache的对象,transactionalCaches是一个HashMap,map的key是Cache对象,map的value是TransactionalCache对象,其中getObject方法可以创建transactionalCaches这个map对象并调用TransactionalCache对象的getObject方法,putObject方法可以创建transactionalCaches这个map对象并调用TransactionalCache对象的putObject方法,commit方法会遍历map的value并循环调用TransactionalCache对象的commit方法
TransactionalCacheManager对象中的transactionalCaches是一个HashMap,map的key是Cache对象,它就是MappedStatement对象中的Cache对象
public class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
private final Cache delegate;
private boolean clearOnCommit;
private final Map<Object, Object> entriesToAddOnCommit;
private final Set<Object> entriesMissedInCache;
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap<>();
this.entriesMissedInCache = new HashSet<>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public Object getObject(Object key) {
// issue #116
Object object = delegate.getObject(key);
if (object == null) {
entriesMissedInCache.add(key);
}
// issue #146
if (clearOnCommit) {
return null;
}
return object;
}
@Override
public void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object);
}
@Override
public Object removeObject(Object key) {
return null;
}
@Override
public void clear() {
clearOnCommit = true;
entriesToAddOnCommit.clear();
}
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
flushPendingEntries();
reset();
}
public void rollback() {
unlockMissedEntries();
reset();
}
private void reset() {
clearOnCommit = false;
entriesToAddOnCommit.clear();
entriesMissedInCache.clear();
}
private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
private void unlockMissedEntries() {
for (Object entry : entriesMissedInCache) {
try {
delegate.removeObject(entry);
} catch (Exception e) {
log.warn("Unexpected exception while notifying a rollback to the cache adapter. "
+ "Consider upgrading your cache adapter to the latest version. Cause: " + e);
}
}
}
}
TransactionalCache对象包含delegate、entriesToAddOnCommit、entriesMissedInCache,delegate表示实际的Cache,entriesToAddOnCommit是一个HashMap,它存放了准备提交的putObject的数据(但是还没有存放到二级缓存),entriesMissedInCache是一个HashSet,它存放了getObject方法获取数据的时候为null的key
putObject方法会将数据存放到entriesToAddOnCommit这个map中,但是还没有存放到二级缓存中
getObject方法会调用delegate的getObject方法,即从二级缓存中获取数据,如果二级缓存中没有这个key就将这个key存放到entriesMissedInCache这个set中,它的作用是在执行commit方法时可以将数据库不存在的key对应的value设置成null并存放到二级缓存中
commit方法会调用delegate.putObject方法,先将entriesToAddOnCommit存放到二级缓存中,然后将entriesMissedInCache中的key对应的value设置成null并调用delegate.putObject方法存储到二级缓存中,这样的话数据库不存在的数据会保存到二级缓存中,它的value为null
5.2 执行流程
查询流程:
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog("lisi");
session.commit(); // 必须加上commit方法二级缓存才生效
System.out.println(blog);
blog = mapper.selectBlog("lisi");
session.commit(); // 必须加上commit方法二级缓存才生效
System.out.println(blog);
}
同一个SqlSession对象查询同一个接口两次,可以看到查询先走CachingExecutor执行器的二级缓存,然后走BaseExecutor执行器的一级缓存,如果都没有就查询数据库,第一次查询会查询数据库并保存到一级缓存但还没有保存到二级缓存中,commit提交的时候会删除一级缓存并将数据存储到二级缓存中,并且对于数据库不存在的key会将value=null存储到二级缓存中,第二次查询会查询二级缓存并返回
注意:二级缓存生效的话必须每次执行完成一个sql语句就执行sqlSession对象的commit方法将数据存储到二级缓存中
第一次查询:
1、执行selectList方法
开启了二级缓存会查询二级缓存的数据发现没有就查询一级缓存,发现也没有就查询数据库,然后将数据存放到一级缓存,还会将数据保存到二级缓存的属性中,但是没有真正的保存到二级缓存中
二级缓存没有数据会查询一级缓存和数据库
一级缓存不存在数据就查询数据库得到结果list,然后放到BaseExecutor执行器的一级缓存localCache这个map中,key是查询的sql语句,value是查询结果list
tcm.putObject(cache, key, list) 会将查询的数据保存到entriesToAddOnCommit中,但此时还没有将数据存放到二级缓存中
2、SqlSession对象执行commit方法
public void commit(boolean required) throws SQLException {
delegate.commit(required);
tcm.commit();
}
SqlSession对象执行commit方法会先调用delegate.commit方法,然后调用tcm.commit()方法
delegate.commit方法会调用BaseExecutor中的localCache的clear方法,清除一级缓存
tcm.commit()方法会将数据保存到二级缓存中
第二次查询:
1、执行selectList方法
第二次查询的时候发现二级缓存已经存在结果就返回结果
5.3 cache标签的作用
cache标签的作用是开启二级缓存,第一次查询的时候会将数据存放到一级缓存中,执行commit方法会将数据存放到二级缓存中并删除一级缓存,第二次查询的时候从二级缓存查询数据并返回结果
标签:缓存,一级,cache,查询,二级缓存,源码,key,Mybatis From: https://blog.csdn.net/weixin_43823462/article/details/140244040