[TOC]
*****
# 7.2 二級緩存
MyBatis的二級緩存在于 SqlSessionFactory 的 生命周期中。 當存在多個SqlSessionFactory時, 它們的緩存都是綁定在各自對象上的, 緩存數據在一般情況下是不相通的。 只有在使用如
Redis這樣的緩存數據庫時, 才可以共享緩存。
## **7.2.1 配置二級緩存**
**全局配置**
在mybatis-config.xml中, 這個參數值默認為 true, 可以不必配置。

*****
MyBatis的二級緩存是和命名空間綁定的, 即二級緩存需要配置在Mapper.xml映射文件中或者配置在 Mapper.java 接口中。
* 在XML中, 命名空間就是 XML 根節點 mapper 的namespace屬性。
* 在Mapper接 口中, 命名空間就是接口的全限定名稱。
### **7.2.1.1 Mapper.xml中配置二級緩存**
在RoleMapper.xml 開啟二級緩存,加cache標簽

#### **默認的二級緩存會有如下效果**
* 映射語句文件中的所有SELECT語句將會被緩存。
* 映射語句文件中的所有INSERT、 UPDATE、 DELETE語句**會刷新二級緩存**。
* *緩存會使用Least Recently Used(LRU, 最近最少使用的) 算法來收回。*
* *根據時間表(如no Flush Interval, 沒有刷新間隔) ,緩存不會以任何時間順序來刷新。*
* 緩存會存儲集合或對象(無論查詢方法返回什么類型的值) 的1024個引用。
* 緩存會被視為read/write(可讀/可寫)的,對象不是共享的, 可以安全地被調用者修改, 并且不干擾其他調用者或線程所做的修改
**二級緩存屬性配置示例**

* 創建了一個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對象

**在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的。