MyBatis缓存专题-一文彻底搞懂MyBatis一级缓存
文章目录1.缓存的概念1.1.什么是缓存1.2.为什么使用缓存1.3.什么样的数据能使用缓存,什么样的数据不能使用2.什么是一级缓存3.什么情况下会命中一级缓存4.Mybatis的一级缓存机制详解5.MyBatis关闭一级缓存6.Mybatis的一级缓存机制源码分析7.Mybatis的一级缓存机制源码分析图解总结8.一级缓存什么时候被清空?9.一级缓存key是什么?10.一级缓存总结带着问题来学习
文章目录
带着问题来学习?
1.缓存的概念
1.1.什么是缓存
存在于内存中的临时数据。
1.2.为什么使用缓存
减少和数据库的交互次数,提高执行效率。(因为查询数据库是一件很费时很费效率的事,还涉及一些硬盘等io操作,而缓存是存在内存中的,读取都很快,而且效率高)
1.3.什么样的数据能使用缓存,什么样的数据不能使用
- 适用于缓存:
经常查询并且不经常改变的。
数据的正确与否对最终结果影响不大的。
- 不适用于缓存:
经常改变的数据
数据的正确与否对最终结果影响很大的。
例如:商品的库存,银行的汇率,股市的牌价。
2.什么是一级缓存
MyBatis 包含了一个非常强大的查询缓存特性,它可以非常方便地配置和定制。MyBatis 3 中的缓存实现的很多改进都已经实现了,使得它更加强大而且易于配置。mybatis 默认情况下只会开启一级缓存,也就是局部的 session 会话缓存。
首先我们要知道什么是查询缓存?查询缓存又有什么作用?
功能:mybatis提供查询缓存,用于减轻数据压力,提高数据库性能。
如下图,每一个 session 会话都会有各自的缓存,这缓存是局部的,也就是所谓的一级缓存:

3.什么情况下会命中一级缓存
- 相同的 sql 和 参数
- 必须是在一个会话
Session当中 - 必须是执行 相同的方法
- 必须是相同的
namespace(同一个命名空间 -> 同一个mapper文件) - 不能够在查询之前执行
clearCache - 中间不能执行 任何
update,delete,insert(会将SqlSession中的数据全部清空)
4.Mybatis的一级缓存机制详解
一级缓存是SqlSession级别的缓存。我们都知道在操作数据库时需要构造 sqlSession对象,而在sqlSession对象中有一个数据结构(HashMap)用于存储缓存数据。
如下图:
从图上,我们可以看出,一级缓存区域是根据SqlSession为单位划分的。每次查询都会先从缓存区域找,如果找不到就会从数据库查询数据,然后将查询到的数据写入一级缓存中。Mybatis内部存储缓存使用的是一个HashMap对象,key为 hashCode + sqlId + sql 语句。而value值就是从查询出来映射生成的java对象。而为了保证缓存里面的数据肯定是准确数据避免脏读,每次我们进行数据修改后(增、删、改操作)就会执行commit操作,清空缓存区域。
/**
* 测试1级缓存
*/
public class TestCache1 {
@Test
public void test1() throws Exception{
try {
String resources = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resources);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserMapper um = sqlSession.getMapper(UserMapper.class);
User user1 = um.findById(1);
User user2 = um.findById(1);
System.out.println(user1==user2);
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
}
运行结果:
用两张图来总结:
第一次:查数据库,放入到缓存中。
第二次:直接从缓存中获取。
下面这段代码中就使用不到缓存
@Test
public void test2() throws Exception{
try {
String resources = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resources);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
UserMapper um1 = sqlSession1.getMapper(UserMapper.class);
UserMapper um2 = sqlSession2.getMapper(UserMapper.class);
User user1 = um1.findById(1);
User user2 = um2.findById(1);
System.out.println(user1==user2);
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
运行结果:
用两张图来总结:
第一次查询:sqlSession1查询数据库,放入到缓存中。
第二次查询:sqlSession2查询数据库,放入到缓存中。
记住是一级缓存只能是同一个SqlSession对象就行了。
5.MyBatis关闭一级缓存
一级缓存也叫本地缓存(LocalCache),Mybatis的一级缓存是会话级别(SqlSession)层面进行缓存的。Mybatis的一级缓存是默认开启的。我们开发项目中不需要做任何配置,但是如果想关闭一级缓存,可以使用localCacheScopde=statement来关闭。
<settings>
<!-- localCacheScope是本地缓存(一级缓存)的作用域,只有两种取值:SESSION和STATEMENT,取STATEMENT意味着关闭一级缓存-->
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
6.Mybatis的一级缓存机制源码分析

SqlSession和Executor都是接口 ,而实现是 DefaultSqlSession 和 CacheExecutor
Client相当于测试方法Test1User@Proxy动态代理Executor接口 才是去数据库中拿数据的跑腿的SqlSession是接口,一级缓存的实现是通过CacheExecutor实现的
程序入口处断点
执行MapperProxy动态代理invoke
MapperMethod调用sqlsession的selectOne方法
本质调用DefaultSqlSession的selectList方法

调用BaseExecutor的query方法
BaseExecutor的query方法生成缓存的key
可以看出缓存key中是包含了 方法和namespace和会话 这些必须相同才会去做一个缓存命中
这里面封装了缓存唯一的key
继续执行BaseExecutor的query方法判断缓存是否存在
如果缓存中不存在,执行查询数据库
缓存中查询到数据存入缓存中

如果缓存中存在,执行查询缓存,PerpetualCache调用getObject方法

7.Mybatis的一级缓存机制源码分析图解总结

DefaultSqlSession中有一个CacheExecutor
CacheExecutor 中有一个 Simpleexexutor
Simpleexexutor 中有一个叫 LocalCache (PerpetualCache类型)
LocalCache才是真正的存储缓存的地方
LocalCache 中有一个叫cache (Hashmap <Object,Object>类型的)
8.一级缓存什么时候被清空?
在执行update、insert、delete、flushCache=“true”、commit、rollback、LocalCacheScope.STATEMENT等情况下,一级缓存就都会被清空。
@Override
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}
update时,一级缓存会被清空。delete和insert都是调用这个update。可以从SqlSession的insert、update、delete方法跟踪。
LocalCacheScope.STATEMENT时,一级缓存会被清空。在BaseExecutor里的query方法中:
事务提交回滚时,一级缓存会被清空。
flushCache="true"时,一级缓存会被清空。
9.一级缓存key是什么?
下面就是一级缓存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<ParameterMapping> 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;
}

key的生成策略:id + offset + limit + sql + param value + environment id,这些值都相同,生成的key就相同。
10.是否会存在数据不一致问题?
不会 因为数据库本身是具有事务隔离级别的 默认是可重复读 也就是说任何一个事务在它内部读到的数据都是一致的,所以并不存在数据不一致的问题
11.一级缓存总结
-
一级缓存的生命周期和SqlSession对象的生命周期一致。所以缓存维护在SqlSession中的属性executor里。
-
一级缓存默认开启。可以通过修改配置项把一级缓存关掉。
-
清空一级缓存的方式有:
update、insert、delete
flushCache="true"
commit、rollback
LocalCacheScope.STATEMENT
更多推荐



所有评论(0)