缓存
介绍缓存之前,首先要搞清楚下面几个问题缓存是什么,作用是什么。
- 什么是缓存?
缓存是mybatis把查询出来的数据做一个记录,在下次查询相同数据时从缓存中去取,就不用去数据库中查找了,类似于浏览器缓存。注意:缓存只对查询有效
- 缓存的作用
使用缓存可以使应用更快地获取数据,避免频繁的数据库交互,尤其是在查询越多、缓存命中率越高的情况下,使用缓存的作用就越明显。MyBatis 作为持久化框架,提供了非常强大 的查询缓存特性,可以非常方便地配置和定制使用。
Mybatis缓存
在Mybatis中,缓存包括两种,分别是一级缓存(也叫本地缓存)和二级缓存。
一级缓存
一级缓存默认开启,并且不能被控制。它是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问。
导致一级缓存失效的原因一般有下面四种
- 不同的SqlSession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两次查询期间执行了任何一次增删改操作
- 同一个SqlSession两次查询期间手动清空了缓存
下面我们测试一下一级缓存效果
<!-- Emp getEmpById(@Param("eid") Long eid);-->
<select id="getEmpById" resultType="Emp">
select * from t_emp where eid = #{eid};
</select>
测试代码:
@Test
public void testCache(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
Emp emp = mapper.getEmpById(1L);
System.out.println(emp);
Emp emp2 = mapper.getEmpById(1L);
System.out.println(emp2);
}
测试结果如下
可以看到当使用同一个sqlsession和查询时,可以直接使用缓存的结果。
这时候有人会问了,如果是查询到同一个结果,但是用的不同的查询语句,那么能直接使用一级缓存中的数据吗?比如上面的张三,先用id查询,再用empname查询,那么还能用一级缓存吗?让我们来测试一下。
<!-- Emp getEmpByEmpName(@Param("empName") String empName);-->
<select id="getEmpByEmpName" resultType="Emp">
select * from t_emp where emp_name = #{empName};
</select>
@Test
public void testCache(){
SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
Emp emp = mapper.getEmpById(1L);
System.out.println(emp);
Emp emp2 = mapper.getEmpByEmpName("张三");
System.out.println(emp2);
}
测试结果如下:
很明显,使用一级缓存必须是同一个sqlsession中;同一个查询方法;同一个查询结果。
一级缓存原理
MyBatis 的一级缓存存在于 SqlSession 的生命周期中,在同一个 SqlSession 中查询时, MyBatis 会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个 Map对象中。如果同一个 SqlSession 中执行的方法和参数完全一致,那么通过算法会生成相同的键值,当 Map 缓存对象中己经存在该键值时,则会返回缓存中的对象。
二级缓存
存在于 SqlSessionFactory 的生命周期中。当存在多个 SqlSessioηFactory 时,它们的缓存都是绑定在各自对象上的,缓存数据在一般情况下是不相通的。只有在使用如 Redis 这样的缓存数据库时,才可以共享缓存。
配置二级缓存
二级缓存配置需要下面几个条件。
- 在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置
- 在映射文件中设置标签<cache />,在xml配置中添加左边这个标签即可。
- 二级缓存必须在SqlSession关闭或提交之后有效
- 查询的数据所转换的实体类类型必须实现序列化的接口(实体类上添加implements Serializable )
下面来一个二级缓存的例子(第二点和第四点需要设置,这里没写):
@Test
public void testTwoCache() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
DynamicSQLMapper mapper1 = sqlSession1.getMapper(DynamicSQLMapper.class);
Emp emp1 = mapper1.getEmpById(1L);
//对应第三点sqlsession关闭
sqlSession1.close();
System.out.println(emp1);
SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
DynamicSQLMapper mapper2 = sqlSession2.getMapper(DynamicSQLMapper.class);
Emp emp2 = mapper2.getEmpById(1L);
System.out.println(emp2);
}
二级缓存失效原因:执行了任意的增删改。
二级缓存属性
-
在mapper配置文件中添加的cache标签可以设置一些属性
-
eviction属性:缓存回收策略
-
LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
- FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
- WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
- 默认的是 LRU
-
flushInterval属性:刷新间隔,单位毫秒
-
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句(增删改)时刷新
-
size属性:引用数目,正整数
-
代表缓存最多可以存储多少个对象,太大容易导致内存溢出
-
readOnly属性:只读,true/false
-
true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
- false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false
MyBatis缓存查询的顺序
- 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- SqlSession关闭之后,一级缓存中的数据会写入二级缓存