MyBatis缓存体系
1.我们想一想MyBatis为什么要用缓存?
首先我们打开百度百科,搜索一波缓存的定义以及工作原理,缓存到底是个啥?
- 缓存的定义
缓存是指可以进行高速数据交换的存储器,它先于内存与CPU交换数据,因此速率很快。L1 Cache(一级缓存)是CPU第一层高速缓存。内置的L1高速缓存的容量和结构对CPU的性能影响较大,不过高速缓冲存储器均由静态RAM组成,结构较复杂,在CPU管芯面积不能太大的情况下,L1级高速缓存的容量不可能做得太大。一般L1缓存的容量通常在32—256KB。L2 Cache(二级缓存)是CPU的第二层高速缓存,分内部和外部两种芯片。内部的芯片二级缓存运行速率与主频相同,而外部的二级缓存则只有主频的一半。L2高速缓存容量也会影响CPU的性能,原则是越大越好,普通台式机CPU的L2缓存一般为128KB到2MB或者更高,笔记本、服务器和工作站上用CPU的L2高速缓存最高可达1MB-3MB。由于高速缓存的速度越高价格也越贵,故有的计算机系统中设置了两级或多级高速缓存。紧靠内存的一级高速缓存的速度最高,而容量最小,二级高速缓存的容量稍大,速度也稍低
缓存的定义有点抽象,了解下就好
- 缓存的工作原理
缓存的工作原理是当CPU要读取一个数据时,首先从CPU缓存中查找,找到就立即读取并送给CPU处理;没有找到,就从速率相对较慢的内存中读取并送给CPU处理,同时把这个数据所在的数据块调入缓存中,可以使得以后对整块数据的读取都从缓存中进行,不必再调用内存。正是这样的读取机制使CPU读取缓存的命中率非常高(大多数CPU可达90%左右),也就是说CPU下一次要读取的数据90%都在CPU缓存中,只有大约10%需要从内存读取。这大大节省了CPU直接读取内存的时间,也使CPU读取数据时基本无需等待。总的来说,CPU读取数据的顺序是先缓存后内存。
简单地说缓存就是把内存当中的一小块区域,把数据加载到这个区域,CPU可以重复利用,减少磁盘IO,减少查找数据的时间。
So,从这一点我们可以理解,MyBatis的缓存也是为了提高查询效率
2.MyBatis的一级缓存
1.在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的SQL,MyBatis提供了一级缓存的方案优化这部分场景,如果是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。
2.一级缓存是 SqlSession级别 的缓存。在操作数据库时需要构造 sqlSession 对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的 sqlSession 之间的缓存数据区域(HashMap)是互相不影响的。
3.一级缓存执行更新或者删除操作,缓存会被清除。
1.接下来说下一级缓存命中的几个条件
缓存命中:就是第一次执行的sql,查询的数据被加载到缓存区,再次执行相同的SQL,直接从内存当中的这块缓存区域读取的数据这一流程。
下面我们用mybatis项目源码test包下的org.apache.ibatis.autoconstructor.AutoConstructorTest来执行演示一级缓存命中的几个条件
1.第一种情况:sql、参数必须相同
@Test
public void testFirstCache1(){
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
PrimitiveSubject subject=mapper.getSubject(1);
PrimitiveSubject subject1=mapper.getSubject(1);
System.out.println(subject==subject1);
}
}
//返回true
2.第二种情况:statementID必须一样
@Test
public void testFirstCache2(){
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
PrimitiveSubject subject=mapper.getSubject(1);
PrimitiveSubject subject1=mapper.getSubject2(1);
System.out.println(subject==subject1);
}
}
//返回false
3.第三种情况:sqlSession必须一样
@Test
public void testFirstCache3(){
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
PrimitiveSubject subject=mapper.getSubject(1);
PrimitiveSubject subject1=sqlSessionFactory.openSession().getMapper(AutoConstructorMapper.class).getSubject(1);
System.out.println(subject==subject1);
}
}
//返回false
4.第四种情况:RowBounds必须相同
@Test
public void testFirstCache4(){
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
RowBounds rowBounds = new RowBounds(0,10);
PrimitiveSubject subject=mapper.getSubject(10);
PrimitiveSubject subject1= (PrimitiveSubject) sqlSession.selectList("org.apache.ibatis.autoconstructor.AutoConstructorMapper.getSubject",10,rowBounds);
System.out.println(subject==subject1);
}
}
//返回false
2.缓存过期的情况
1.第一种情况:通过同一个SqlSession执行更新操作时,这个更新操作不仅仅指代update操作,还指插入和删除操作
/**
* 更新操作
* @param id
* @return
*/
@Update("update subject set age=30 WHERE id = #{id}")
boolean setSubject(final int id);
/**
* 缓存失效:1.更新操作
*/
@Test
public void testFirstCache5(){
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
PrimitiveSubject subject=mapper.getSubject(1);
//更新操作
Boolean subject2=mapper.setSubject(1);
PrimitiveSubject subject3=mapper.getSubject(1);
System.out.println(subject==subject3);
}
}
//返回false
2.第二种情况:事务提交时会删除一级缓存
/**
* 缓存失效:2.sqlSession提交
*/
@Test
public void testFirstCache6(){
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
PrimitiveSubject subject=mapper.getSubject(1);
//sqlSession提交
sqlSession.commit();
PrimitiveSubject subject3=mapper.getSubject(1);
System.out.println(subject==subject3);
}
}
//返回false
3.第三种情况:事务回滚时也会删除一级缓存
/**
* 缓存失效:3.sqlSession回滚
*/
@Test
public void testFirstCache7(){
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
PrimitiveSubject subject=mapper.getSubject(1);
//sqlSession回滚
sqlSession.rollback();
PrimitiveSubject subject3=mapper.getSubject(1);
System.out.println(subject==subject3);
}
}
//返回false
3.MyBatis集成Spring造成一级缓存失效问题
很多人发现,MyBatis集成Spring造成一级缓存失效,以为是Spring的bug,其实是Spring对SqlSession进行了封装,通过SqlSessionTemplate,使得每次调用Sql,都会重新创建一个SqlSession。一级缓存必须是同一会话才能命中,所以这些场景不能命中。
解决办法:给Spring添加事务即可,添加事务之后,SqlSessionInterceptor(会话拦截器)就会判断两次请求是否在同一事务当中,如果是就会用同一个SqlSession来解决。
3.MyBatis的二级缓存
1.二级缓存是应用级别的缓存,与一级缓存不同的是,它的作用范围是整个应用,并且可以跨线程使用。所以二级缓存有更高的命中率,适合缓存一些较少的数据。在流程上是先访问二级缓存,在访问一级缓存。之所以称之为“二级缓存”,是相对于“一级缓存”而言的。既然有了一级缓存,那么为什么要提供二级缓存呢?我们知道,在一级缓存中,不同session进行相同SQL查询的时候,是查询两次数据库的。显然这是一种浪费,既然SQL查询相同,就没有必要再次查库了,直接利用缓存数据即可,这种思想就是MyBatis二级缓存的初衷。
2.另外,Spring和MyBatis整合时,每次查询之后都要进行关闭sqlsession,关闭之后数据被清空。所以MyBatis和Spring整合之后,一级缓存是没有意义的。如果开启二级缓存,关闭sqlsession后,会把该sqlsession一级缓存中的数据添加到mapper namespace的二级缓存中。这样,缓存在sqlsession关闭之后依然存在。
1.开启二级缓存
在mybatis-config.xml中加入如下配置,开启二级缓存
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
mapper.xml文件
<cache/>
2.使用二级缓存
@Test
public void testFirstCache8(){
SqlSession sqlSession1 = sqlSessionFactory.openSession();
AutoConstructorMapper mapper1 = sqlSession1.getMapper(AutoConstructorMapper.class);
SqlSession sqlSession2 = sqlSessionFactory.openSession();
AutoConstructorMapper mapper2 = sqlSession2.getMapper(AutoConstructorMapper.class);
PrimitiveSubject subject1=mapper1.getSubject(1);
sqlSession1.commit();
PrimitiveSubject subject2=mapper2.getSubject(1);
System.out.println(subject1==subject2);
}
3.二级缓存清除策略
cache标签下面有下面几种可选项
eviction: 缓存回收策略,支持的策略有下面几种
-
LRU - 最近最少回收,移除最长时间不被使用的对象(默认是这个策略)
-
FIFO - 先进先出,按照缓存进入的顺序来移除它们
-
SOFT - 软引用,移除基于垃圾回收器状态和软引用规则的对象
-
WEAK - 弱引用,更积极的移除基于垃圾收集器和弱引用规则的对象
flushinterval:缓存刷新间隔,缓存多长时间刷新一次,默认不清空,设置一个毫秒值;
readOnly: 是否只读;true 只读 ,MyBatis 认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。MyBatis 为了加快获取数据,直接就会将数据在缓存中的引用交给用户。不安全,速度快。读写(默认):MyBatis 觉得数据可能会被修改
size: 缓存存放多少个元素
type: 指定自定义缓存的全类名(实现Cache 接口即可)
blocking:若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache。
4.二级缓存使用建议
MyBatis的二级缓存实用性不是很大。一个原因就是Spring环境下,一本只有一个SqlSession,不存在sqlSession之间共享缓存;还有就是MyBatis的缓存都不能做到分布式,所以对于MyBatis的二级缓存以了解为主。
总结
一级缓存与二级缓存的不同之处:
- 1、Mybatis的一级缓存默认开启,而二级缓存默认关闭。
- 2、Mybatis的一级缓存指的是Mybaits中SqlSession对象的缓存,而二级缓存指的是SqlSessionFactory对象的缓存。一个SqlSessionFactory对象包括多个SqlSession对象。
- 3、SqlSession对象中存放的是返回数据的对象,而SqlSessionFactory对象中存放的是数据,不是对象。
- 4、Mybatis和Spring整合的时候,一级缓存与事务有关,而二级缓存与事务无关。