[TOC]
*****
# 7.2 二级缓存
MyBatis的二级缓存在于 SqlSessionFactory 的 生命周期中。 当存在多个SqlSessionFactory时, 它们的缓存都是绑定在各自对象上的, 缓存数据在一般情况下是不相通的。 只有在使用如
Redis这样的缓存数据库时, 才可以共享缓存。
## **7.2.1 配置二级缓存**
**全局配置**
在mybatis-config.xml中, 这个参数值默认为 true, 可以不必配置。
![](https://box.kancloud.cn/751b7875e5e3cc84ae0568b4e658c02e_589x122.png)
*****
MyBatis的二级缓存是和命名空间绑定的, 即二级缓存需要配置在Mapper.xml映射文件中或者配置在 Mapper.java 接口中。
* 在XML中, 命名空间就是 XML 根节点 mapper 的namespace属性。
* 在Mapper接 口中, 命名空间就是接口的全限定名称。
### **7.2.1.1 Mapper.xml中配置二级缓存**
在RoleMapper.xml 开启二级缓存,加cache标签
![](https://box.kancloud.cn/fcb0c70430819e26833f097dd0b5ab4d_721x256.png)
#### **默认的二级缓存会有如下效果**
* 映射语句文件中的所有SELECT语句将会被缓存。
* 映射语句文件中的所有INSERT、 UPDATE、 DELETE语句**会刷新二级缓存**。
* *缓存会使用Least Recently Used(LRU, 最近最少使用的) 算法来收回。*
* *根据时间表(如no Flush Interval, 没有刷新间隔) ,缓存不会以任何时间顺序来刷新。*
* 缓存会存储集合或对象(无论查询方法返回什么类型的值) 的1024个引用。
* 缓存会被视为read/write(可读/可写)的,对象不是共享的, 可以安全地被调用者修改, 并且不干扰其他调用者或线程所做的修改
**二级缓存属性配置示例**
![](https://box.kancloud.cn/d162b5ea9eb6d84408a64abdc5419936_363x189.png)
* 创建了一个FIFO缓存
* 每隔60秒刷新一次
* 存储集合或对象的512个引用
* 返回的对象被认为是只读的, 在不同线程中的调用者之间修改它们会导致冲突。
*****
#### **cache可以配置的属性**
**eviction(收回策略)**
* LRU(最近最少使用的) : 移除最长时间不被使用的对象, 这是默认值。
* FIFO(先进先出) : 按对象进入缓存的顺序来移除它们。
* *SOFT(软引用) : 移除基于垃圾回收器状态和软引用规则的对象。*
* *WEAK(弱引用) : 更积极地移除基于垃圾收集器状态和弱引用规则的对象。*
**flushInterval(刷新间隔)** 。 可以被设置为任意的正整数, 而且它们代表一个合理的毫秒形式的时间段。 默认情况不设置, 没有刷新间隔, 缓存仅仅在调用语句(insert,update,delete)时刷新。
**size(引用数目**) 。 可以被设置为任意正整数, 要记住缓存的对象数目和运行环境的可用内存资源数目。 默认值是1024。
**readOnly(只读)**
* 只读,true。给调用者返回缓存对象的相同实例。性能有优势,对象不能被修改。
* 可读写,false。通过序列化返回缓存对象的拷贝(不同的实例)。 慢, 但安全。
## **7.2.2 使用二级缓存**
* MyBatis使用SerializedCache(org.apache.ibatis.cache.decorators.SerializedCache)序列化缓存来实现可读写缓存类, 并通过序列化和反序列化来通过缓存获取数据时, 得到的是一个新的实例。
* 因为使用可读写缓存,可以使用SerializedCache序列化缓存,这个缓存类要求所有被序列化的对象必须实现Serializable(java.io.Serializable) 接口, 所以还需要修改SysRole对象
![](https://box.kancloud.cn/76b400762dfd9bf32eb4ed2f2dd5eda9_794x266.png)
**在CacheTest.java中测试二级缓存**
```
@Test
public void testL2Cache(){
//获取 sqlSession
SqlSession sqlSession = getSqlSession();
SysRole role1 = null;
try {
//获取 RoleMapper 接口
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
//调用 selectById 方法,查询 id = 1 的用户
role1 = roleMapper.selectById(1l);
//对当前获取的对象重新赋值
role1.setRoleName("New Name");
//再次查询获取 id 相同的用户
SysRole role2 = roleMapper.selectById(1l);
//虽然我们没有更新数据库,但是这个用户名和我们 role1 重新赋值的名字相同了
Assert.assertEquals("New Name", role2.getRoleName());
//不仅如何,role2 和 role1 完全就是同一个实例 因为此时默认使用一级缓存,返回同一个实例
Assert.assertEquals(role1, role2);
} finally {
//关闭当前的 sqlSession
sqlSession.close();
}
System.out.println("开启新的 sqlSession");
//开始另一个新的 session
sqlSession = getSqlSession();
try {
//获取 RoleMapper 接口
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
//调用 selectById 方法,查询 id = 1 的用户
SysRole role2 = roleMapper.selectById(1l);
//第二个 session 获取的用户名仍然是 New Name
Assert.assertEquals("New Name", role2.getRoleName());
//这里的 role2 和 前一个 session 查询的结果是两个不同的实例
Assert.assertNotEquals(role1, role2);
//获取 role3
SysRole role3 = roleMapper.selectById(1l);
//这里的 role2 和 role3 是两个不同的实例
Assert.assertNotEquals(role2, role3);
} finally {
//关闭 sqlSession
sqlSession.close();
}
}
```
在第一个try块中使用的是一级缓存, 所以role1和role2是同一个实例。
在第二个try块中,当调用close方法关闭SqlSession时, SqlSession才会保存查询数据到二级缓存中。 在这之后二级缓存才有了缓存数据。role2和 role3都是反序列化得到的结果, 所以它们不是相同的实例。 在这一部分, 这两个实例是读写安全的, 其属性不会互相影响。
**只读缓存**
配置为只读缓存,MyBatis就会使用Map来存储缓存值, 从缓存中获取的对象就是同一个实例。
#### **注意**
在这个例子中并没有真正的读写安全, 为什么?因为这个测试中加入了一段不该有的代码, 即role1.setRoleName("New Name"); , 这里修改role1的属性值后, 按照常理应该更新数据, 更新后会清空一、 二级缓存, 这样就可以避免人
为产生的脏数(数据库里的roleName和缓存中的roleName不一样), 避免缓存和数据库的数据不一致。 所以想要安全使用, 需要避免毫无意义的修改。
#### **总结**
**一级缓存:** 当一个SqlSession结束后该SqlSession中的一级缓存也就不存在了。
**二级缓存:** 是mapper级别的缓存。使用二级缓存时,多个SqlSession使用同一个Mapper的sql语句去操作数据库,得到的数据会存在二级缓存区域,它同样是使用HashMap进行数据存储。相比一级缓存SqlSession,二级缓存的范围更大,多个Sqlsession可以共用二级缓存,二级缓存是跨SqlSession的。