一级缓存测试
1)相同会话
关闭二级缓存
<!-- 控制全局缓存(二级缓存),默认 true-->
<setting name="cacheEnabled" value="false"/>
@Test
public void testCache() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session1 = sqlSessionFactory.openSession();
SqlSession session2 = sqlSessionFactory.openSession();
try {
BlogMapper mapper0 = session1.getMapper(BlogMapper.class);
BlogMapper mapper1 = session1.getMapper(BlogMapper.class);
System.out.println("第一次查询~~~~~都是同一会话session1~~~~~~~~");
Blog blog = mapper0.selectBlogById(1);
System.out.println(blog);
System.out.println("第二次查询~~~~~都是同一会话session1~~~~~");
System.out.println(mapper1.selectBlogById(1));
} finally {
session1.close();
}
}
可以看到只有第一次打印SQL执行过程信息,第二次没有打印就是从cache里获取了
2)不同会话
@Test
public void testCache() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session1 = sqlSessionFactory.openSession();
SqlSession session2 = sqlSessionFactory.openSession();
try {
BlogMapper mapper0 = session1.getMapper(BlogMapper.class);
System.out.println("第一次查询~~~~~~~~~~session1和session2不同会话~~~~~~~");
Blog blog = mapper0.selectBlogById(1);
System.out.println(blog);
System.out.println("第二次查询~~~~~~~~~~session1和session2不同会话~~~~~~~");
BlogMapper mapper1 = session2.getMapper(BlogMapper.class);
System.out.println(mapper1.selectBlogById(1));
} finally {
session1.close();
}
}
3)更新之后,一级缓存被清空
关键代码
BlogMapper mapper = session.getMapper(BlogMapper.class);
System.out.println(mapper.selectBlogById(1));
Blog blog = new Blog();
blog.setBid(1);
blog.setName("after modified 666");
mapper.updateByPrimaryKey(blog);
session.commit();
// 相同会话执行了更新操作,缓存是否被清空?
System.out.println("在[同一个会话]执行更新操作之后,是否命中缓存?");
System.out.println(mapper.selectBlogById(1));
结果:
4)被其他会话更新后,一级缓存会读取过时数据
BlogMapper mapper1 = session1.getMapper(BlogMapper.class);
System.out.println("第一次查询~~~~~~~~~~~~~");
System.out.println(mapper1.selectBlogById(1));
// 会话2更新了数据,会话2的一级缓存更新
Blog blog = new Blog();
blog.setBid(1);
blog.setName("after modified 555555555555");
BlogMapper mapper2 = session2.getMapper(BlogMapper.class);
mapper2.updateByPrimaryKey(blog);
session2.commit();
// 其他会话更新了数据,本会话的一级缓存还在么?
System.out.println("被会话2更新后,第二次查询会话1查到最新的数据了吗?");
System.out.println(mapper1.selectBlogById(1));
所以一级缓存工作范围太小,会从cache中读取到过时数据。
二级缓存
1)配置
作用域:namespace,范围就是在一个接口Mapper
<mapper namespace="com.gupaoedu.mapper.BlogMapper">
总开关在mybatis-config.xml中配置,默认是开启的,可以手动关闭
<!-- 控制全局缓存(二级缓存),默认 true-->
<setting name="cacheEnabled" value="true"/>
然后在具体的Mapper.xml中和也需要配置<cache>标签,如果不配置,即使二级缓存总开关是打开的,也不会走二级缓存.
<cache type="org.apache.ibatis.cache.impl.PerpetualCache"
size="1024"
eviction="LRU"
flushInterval="120000"
readOnly="false"/>
开启二级缓存后,如果针对某一个方法不用二级缓存,可以单独配置 useCache="false" :
<select id="selectBlogWithAuthorQuery" resultMap="BlogWithAuthorQueryMap" useCache="false" > ...</select>
注意:useCache 是否启用二级缓存;flushCache是否清空二级缓存
flushCache="true" 是否清空二级缓存: 增删改语句默认为true,查询语句默认是false,如果查询语句想要每次都清空缓存可以显示配置flushCache="true":
<select id="selectBlogWithAuthorResult" resultMap="BlogWithAuthorResultMap" flushCache="true" >
2)测试
@Test
public void testCache() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session1 = sqlSessionFactory.openSession();
SqlSession session2 = sqlSessionFactory.openSession();
try {
BlogMapper mapper1 = session1.getMapper(BlogMapper.class);
System.out.println("第一次查询~~~~~~~");
System.out.println(mapper1.selectBlogById(1));
// 事务不提交的情况下,二级缓存会写入吗?
session1.commit();
BlogMapper mapper2 = session2.getMapper(BlogMapper.class);
System.out.println("第二次查询~~~~~~~");
System.out.println(mapper2.selectBlogById(1));
} finally {
session1.close();
}
}
注意:如果把session1.commit();注释了,就不走缓存了,是因为查询的结果会先到待提交的队列中,并不会真正写入到cache中。
二级缓存是和事务绑定在一起的。
3)二级缓存是和事务关联在一起的
在CachingExecutor中:
private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
// cache 对象是在哪里创建的? XMLMapperBuilder类 xmlconfigurationElement()
// 由 <cache> 标签决定
if (cache != null) {
// flushCache="true" 清空一级二级缓存 >>
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
// 获取二级缓存
// 缓存通过 TransactionalCacheManager、TransactionalCache 管理
@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;
}
}
// 走到 SimpleExecutor | ReuseExecutor | BatchExecutor
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public class TransactionalCacheManager {
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
...
}
public class TransactionalCache implements Cache {
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
// 真正写入二级缓存
flushPendingEntries();
reset();
}
}
* 为啥二级缓存要个事务绑定在一块呢?
其实就是怕回滚,查询了之后,马上写入缓存,如果后面的commit发生问题,就是数据库没数值,但是缓存里有值,就出现了问题。
* 一级缓存为啥不用和事务绑定呢?
因为一级缓存中如果出现问题,事务回滚了,会话结束了,缓存本身就没有了,不存在跨会话的问题。
4)被其他会话中执行了更新操作,二级缓存会被清空
@Test
public void testCacheInvalid() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session1 = sqlSessionFactory.openSession();
SqlSession session2 = sqlSessionFactory.openSession();
SqlSession session3 = sqlSessionFactory.openSession();
try {
BlogMapper mapper1 = session1.getMapper(BlogMapper.class);
BlogMapper mapper2 = session2.getMapper(BlogMapper.class);
BlogMapper mapper3 = session3.getMapper(BlogMapper.class);
System.out.println("第一次查询~~~~~~~");
System.out.println(mapper1.selectBlogById(1));//session1
session1.commit();
// 是否命中二级缓存
System.out.println("第二次查询~~~~~~~");
System.out.println(mapper2.selectBlogById(1));//session2
Blog blog = new Blog();
blog.setBid(1);
blog.setName("2019年1月6日15:03:38");
mapper3.updateByPrimaryKey(blog);
session3.commit();//session3
System.out.println("第三次查询~~~~~~~");
// 在其他会话中执行了更新操作,二级缓存是否被清空?
System.out.println(mapper2.selectBlogById(1));//session2
} finally {
session1.close();
session2.close();
session3.close();
}
}