文章目录
1 一级缓存
Mybatis的一级缓存是默认开启的
,它只相对于同一个SqlSession有效,所以也称之为SqlSession缓存。
当参数和SQL完全相同的情况下,使用同一个SqlSession对象调用同一个Mapper方法,当第1次执行SQL语句后,MyBatis会自动将其放在缓存中,后续再次查询时,如果没有声明需要刷新,且缓存没有超时,会直接取出此前缓存的数据,而不会再次发送SQL到数据库。
例如:
public void selectUserById(int selectId)
{
SqlSession session = sqlSessionFactory.openSession();
try{
User user = session.selectOne("org.mybatis.findUserById", selectId);
User user2 = session.selectOne("org.mybatis.findUserById", selectId);
} finally {
session.close();
}
}
如上述代码所示,在同一个SqlSession下,进行2次同样sql的操作,并且入参相同,此时,第二次查询就会从缓存中读取数据,避免和数据库产生真实的查询操作。
1.1 关闭一级缓存
MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION
,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT
,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。
全局配置文件,settings下参数名localCacheScope,例如:
<configuration>
<settings>
<setting name="localCacheScope" value="SESSION"/>
</settings>
</configuration>
默认值是SESSION,改为STATEMENT即可表示关闭。
1.2 一级缓存失效
在大部分的情况下一级缓存是可以的,但是有几种特殊的情况会造成一级缓存失效.。
1.2.1 开启多个sqlSession
一级缓存是sqlSession级别的缓存,如果在应用程序中只有开启了多个sqlsession,那么会造成缓存失效
//开启一个sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
List<Emp> list = mapper.selectAllEmp();
System.out.println("================================");
//新开启一个sqlSession
SqlSession sqlSession2 = sqlSessionFactory.openSession();
EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class);
List<Emp> list2 = mapper2.selectAllEmp();
上述代码展示了2个不同的SqlSession,会导致缓存失效。
1.2.2 参数不一致
在编写查询的sql语句的时候,一定要注意传递的参数,如果参数不一致,那么也不会缓存结果:
SqlSession session = sqlSessionFactory.openSession();
try{
//入参是1
User user = session.selectOne("org.mybatis.findUserById", 1);
//入参是2
User user2 = session.selectOne("org.mybatis.findUserById", 2);
} finally {
session.close();
}
上述代码展示了2个不同的入参,虽然是同一个SqlSession ,但仍会导致缓存失效。
1.2.3 如果在发送过程中发生了数据的修改,那么结果就不会缓存
数据的修改包括增删改。
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
//执行一次查询A
Emp empByEmpno = mapper.findEmpByEmpno(1111);
//执行一次修改操作
empByEmpno.setEname("zhangsan");
mapper.updateEmp(empByEmpno);
//再执行一次查询A
Emp empByEmpno1 = mapper.findEmpByEmpno(1111);
上述代码,虽然2次查询操作一模一样,甚至是同一个SqlSession ,但是2次查询操作之间产生一次修改数据的操作,也会导致缓存失效。
1.2.4 在两次查询期间,手动去清空缓存,也会让缓存失效
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
//执行一次查询A
Emp empByEmpno = mapper.findEmpByEmpno(1111);
//手动执行一次清除缓存操作
sqlSession.clearCache();
//再执行一次查询A
Emp empByEmpno1 = mapper.findEmpByEmpno(1111);
1.3 一级缓存是何时加入缓存的?
首先我们要知道一级缓存是如何实现的,很简单,就是一个hashmap。
一级缓存默认实现类PerpetualCache ,使用map进行存储的,格式是key==> sqlId+sql
,其中sql是指入参解析后的sql,也就是为什么入参变化会导致一级缓存失效的原因。
一级缓存查询完就会进行存储,和事务无关。二级缓存则复杂些,必须提交事务,才会放入二级缓存。
2. 二级缓存
二级缓存是全局作用域缓存,默认是不开启的,需要手动进行配置。
Mybatis提供二级缓存的接口以及实现,缓存实现的时候要求实体类实现Serializable接口,二级缓存在sqlSession关闭或提交之后才会生效。
简单来说,二级缓存可以跨sqlSession生效。
特点:
(1)二级缓存是mapper级别的缓存,使用二级缓存时,多个SqlSession使用同一个mapper的sql语句去操作数据库,得到的数据存在二级缓存区域。同样是使用hashMap进行存储。
什么是mapper级别?就是说多个mapper文件,可以设置指定部分mapper加入二级缓存,其余可以不加入缓存
(2)相比于一级缓存,二级缓存的范围更大,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
(3)二级缓存是多个SqlSession共享的,其作用域是SqlSession的namespace。Mybatis一级缓存是默认开启的,二级缓存全局也默认开启,但是额外需要在mapper配置文件中开启二级缓存,避免默认把所有的mapper都加入缓存,内存吃不消。
2.1 开启二级缓存
步骤:
1、全局配置文件中添加如下配置:
<setting name="cacheEnabled" value="true"/>
这个参数可以理解为全局开关,总开关。默认是打开的,也就是说步骤1可以省略。
2、需要在使用二级缓存的映射文件出使用<cache/>
标签标注
假设存在映射文件ABC,我只希望A里面的sql可以被缓存,那么在A里面设置
<cache/>
标签即可。作用是避免全部的sql都缓存,减少内存消耗。
3、实体类必须要实现Serializable接口。
开启二级缓存后,如果不实现Serializable接口,运行时会报错。
配置成功就会出现缓存命中率,对同一个sqlId,打印从缓存中命中的次数/查询总次数
例:
Cache hit ratio :0.5
2.2 缓存的属性
eviction
:表示缓存回收策略,默认是LRU
LRU:最近最少使用的,移除最长时间不被使用的对象
FIFO:先进先出,按照对象进入缓存的顺序来移除
SOFT:软引用,移除基于垃圾回收器状态和软引用规则的
对象
WEAK:弱引用,更积极地移除基于垃圾收集器状态和弱引
用规则的对象flushInternal
:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语
句时刷新size
:引用数目,正整数代表缓存最多可以存储多少个对象,太大容易导致内存溢出readonly
:只读,true/false
true:只读缓存,会给所有调用这返回缓存对象的相同实例,因此这些对象不能被修改。
false:读写缓存,会返回缓存对象的拷贝(序列化实现),这种方式比较安全,默认值
2.2 二级缓存何时失效?
首先,二级缓存是有过期时间的,就是2.1中的flushInterval
参数控制的,默认是1小时,意味着到时会清空整个缓存cache。
一级缓存没有过期时间的概念
此外,和一级缓存类似:
-
1.同一个命名空间进行了增删改的操作,会导致二级缓存失效。
绝大多数情况下,我们希望是增删改操作能触发失效,可以避免脏数据, 但是如果不想失效:可以将SQL的flushCache 设置为false:<!--默认值是true,表示会触发失效--> <delete id="deleteAuthor" flushCache="false">
改参数对
<update><insert>
也生效。注意<select>
也有flushCache参数,但是语义不同。但是要慎重设置,因为会造成数据脏读问题,除非你能保证查询的数据永远不会执行增删改
-
2.让查询不缓存数据到二级缓存中useCache=“false”
对于<select>
标签,useCache默认值为true,表示每次查询使用二级缓存,若为false,则表示永不加入缓存,作用是定义某个mapper内的某个sql被排除加入缓存中,提供灵活度 -
3.如果希望其他命名空间的mapper映射文件B执行了增删改会关联清空当前的命名空间A的缓存,就可以在A中设置:
<cache-ref namespace="com.someone.application.data.BMapper"/>
这个方法有个缺点,就是只能填一个关联mapper,不支持同时关联多个
注意:sqlSession.clearCache()不会清除二级缓存,只会清除一级缓存。
2.3 二级缓存的应用场景
PerpetualCache只能在单台服务器上有效,如果在多台服务器上就没有效果了,此时我们可以使用分布式缓存来实现在所有服务器上二级缓存都生效,常用的分布式缓存有redis、ehcache、memcache等。
那么究竟应该不应该使用二级缓存呢?先来看一下二级缓存的注意事项:
- 缓存是以namespace为单位的,不同namespace下的操作互不影响。
- insert,update,delete操作会清空所在namespace下的全部缓存, 不能做到部分删除。
- 通常使用MyBatis Generator生成的代码中,都是各个表独立的,每个表都有自己的namespace。
二级缓存默认是单机模式的,如果要支持分布式还要配置redis,而且二级缓存不支持单条数据的更新,一旦发生insert/update/delete操作会一次性清空缓存,还不如直接使用redis作为缓存服务器来的简单,所以二级缓存并不推荐使用。
2.3.1 适用的场景
- 对查询频率高,变化频率低的数据建议使用二级缓存。
- 对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis二级缓存技术降低数据库访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。
实现方法如下:
通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟、24小时等,根据需求而定。