一、原理
第一次
SqlSession1
去查詢用戶id
為1的用戶信息,查詢到用戶信息將會將查詢到的數據存儲到二級緩存中。第二次
SqlSession2
去查詢用戶id
為1的用戶信息,先去緩存中找,如果緩存有則直接從緩存中取數據,如果沒有則去數據庫中查詢。二級緩存與一級緩存的區別,二級緩存的范圍更大,多個
SqlSession
可以共享一個UserMapper
的二級緩存區域。UserMapper
有一個二級緩存區域(這個是按照namespace
劃分),其他mapper
也有自己的二級緩存區域(按照namespace
劃分)。每一個namespace
的mapper
有一個二級緩存區域。也就是說如果兩個mapper
的namespace
相同,那么這兩個mapper
執行sql
查詢到的數據將存儲在一個二級緩存區域中。當然我們如果在兩次查詢之間加入提交操作,則同樣會清空二級緩存的。這樣第二次查詢的時候也要發出
sql
去數據庫中查詢。
二、測試
- 開啟二級緩存(工程
mybatis13
)
除了在SqlMapConfig.xml
中開啟二級緩存的總開關,還要在具體的mapper.xml
中開啟二級緩存。
SqlMapConfig.xml
<settings>
<!-- 打開延遲加載的開關,再將積極加載改為消極加載(即按需加載) -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 開啟二級緩存,這里設置是為了方便管理,本身默認是開啟的 -->
<setting name="cacheEnabled" value="true"/>
</settings>
UserMapper.xml
<!-- 開啟本mapper的namespace下的二級緩存 -->
<cache />
說明:其實在全局配置文件中配置主要是為了便于管理。在UserMapper.xml
中開啟二級緩存,UserMapper.xml
下的sql
執行完成會存儲到它的緩存區域(HashMap
)。
索引 | 描述 | 允許值 | 默認值 |
---|---|---|---|
cacheEnabled |
對在此配置文件下的所有cache 進行全局性開/關設置 |
true/false |
true |
- 設置
pojo
類實現序列換接口,為了將緩存數據取出執行反序列化操作,因為二級緩存數據存儲介質多種多樣,不一定在內存中。
User.java
public class User implements Serializable{
private Integer id ;
private String username ;
private String sex ;
private Date birthday ;
private String address ;
//用戶創建的訂單列表
private List<Orders> ordersList ;
....
}
- 測試
UserMapperTest.java
//二級緩存測試
@Test
public void testCache2() throws Exception{
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
//第一次發起請求,查詢id為1的用戶
User user1 = userMapper1.findUserById(1);
System.out.println(user1);
sqlSession1.close();//如果不關閉,SqlSession數據是不能寫到二級緩存區域的
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
//第二次發起請求,查詢id為1的用戶
User user2 = userMapper2.findUserById(1);
System.out.println(user1);
sqlSession2.close();
}
此時的測試結果為:
可以看到總共只是發出了一條
sql
語句,同時注意到有一項信息Cache Hit Retio
,這表示緩存命中率,第一次的時候緩存中沒有數據則命中率是0.0,第二次先從一級緩存中找,沒有;再從二級緩存中找,找到了,此時緩存命中率是0.5。
- 加入提交
//二級緩存測試
@Test
public void testCache2() throws Exception{
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
//第一次發起請求,查詢id為1的用戶
User user1 = userMapper1.findUserById(1);
System.out.println(user1);
sqlSession1.close();//如果不關閉,SqlSession數據是不能寫到二級緩存區域的
//執行提交操作
UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
User user = userMapper3.findUserById(1);
user.setUsername("狗蛋");
userMapper3.updateUser(user);
sqlSession3.commit();//執行提交清空UserMapper下的二級緩存
sqlSession3.close();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
//第二次發起請求,查詢id為1的用戶
User user2 = userMapper2.findUserById(1);
System.out.println(user1);
sqlSession2.close();
}
此時我們在測試的時候就會發現發出了兩條查詢語句。
三、二級緩存其他一些參數
3.1 禁用二級緩存
- 在
statement
中設置userCache="false"
可以禁用當前select
語句的二級緩存,即每次查詢都會發出sql
去查詢,默認情況是true
,即該sql
使用二級緩存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" userCache = "false">
總結:針對每次查詢都需要最新的數據,要設置成禁用二級緩存。
3.2 刷新緩存
在
mapper
的同一個namespace
中,如果有其它的insert、update、delete
操作數據后需要刷新緩存,如果不執行,則緩存中可能出現臟數據。設置
statement
中的flushCache="true"
屬性,默認情況下為true
,即刷新緩存,如果改成false
則不會刷新。使用緩存時如果手動修改數據庫表中的查詢數據會出現臟讀。如下:
<insert id="insertUser" parameterType="User" flushCache="true">
注意:這里刷新緩存就是清空緩存。
總結:一般情況下,執行完commit
操作都需要刷新緩存,flushCache="true"
表示刷新緩存,這樣可以避免數據庫臟讀。
3.3 Mybatis Cache參數
-
flushInterval
(刷新間隔)可以被設置為任意的正整數,而且它們代表一個合理的毫秒形成的時間段。默認情況下,也就是沒有刷新問題,緩存僅僅調用語句時刷新。 -
size
(引用數目)可以被設置為任意正整數,要記住你緩存的對象數目和你運行環境的可用內存資源數目。默認值是1024 -
readOnly
(只讀)屬性可以被設置為true
或false
。只讀的緩存會給所有的調用者返回緩存對象的相同示例。因此這些對象不能被修改。這提供了很重要的性能優勢。可讀寫的緩存會返回緩存對象的拷貝(通過序列化)。這會慢一些,但是安全,因此默認是false
。
如下例子:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
這個更高級的配置創建了一個FIFO
緩存,并每隔60秒刷新,存儲結果對象或列表的512個引用,而且返回的對象被認為是只讀,因此在不同線程中的調用者之間修改它們會導致沖突。可用的解決策略有,默認的是LRU
:
- 1.
LRU
:最近最少使用的:移除最長時間不被使用的對象 - 2.
FIFO
:先進先出:按對象進入緩存的順序來移除它們 - 3.
SOFT
: 軟引用:移除基于垃圾回收器狀態和軟引用規則的對象。 - 4.
WEAK
: 弱引用:更積極地移除基于垃圾收集器狀態和弱引用規則的對象。
3.4 整合EhCache
EhCache是一個分布式的緩存框架。即使我們使用mybatis
的二級緩存,查詢出來的相關數據也只是保存在單個服務器上,所以我們需要使用分布式的緩存。
- 導入相關的
jar
包:(工程mybatis14
)
ehcache-core-2.6.8.jar
mybatis-ehcache-1.0.3.jar
- 加入EhCache的配置文件
config/ehcache.xml
:
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="E:\ehcache" />
<defaultCache
maxElementsInMemory="10000"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
- 在相關的
UserMapper.xml
文件中進行配置:
<!-- 開啟本mapper的namespace下的二級緩存,
type指定cache接口的實現類的接口,mybatis默認使用PerpetualCache類,我們要和
EhCache整合,需要配置type為EhCache的實現cache接口的類 -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
之前我們只是開啟了二級緩存,默認是使用mybatis
的二級緩存,這里我們配置使用EhCache
緩存。
- 測試
測試文件不需要改動,直接測試即可,我們發現還是可以實現二級緩存的效果。
四、二級緩存的應用場景
對于訪問多的查詢請求且用戶對查詢結果實時性要求不高,此時可采用
mybatis
的二級緩存技術降低數據庫的訪問量,提高訪問速度,業務場景比如:耗時較高的統計分析sql
、電話賬單查詢sql
等。實現方法如下:通過設置刷新間隔時間,由
mybatis
每隔一段時間自動清空緩存,根據數據變化頻率設置緩存刷新間隔flushInterval
,比如設置為30分鐘、60分鐘等,根據需求而定。
五、局限性
Mybatis
二級緩存對細粒度的數據級別的緩存實現不好,比如如下需求:對商品信息進行緩存,由于商品信息查詢訪問量大,但是要求用戶每次都能查詢最新的商品信息,此時如果使用mybatis
的二級緩存就無法實現當一個商品變化時只刷新該商品的緩存信息而不刷新其他商品的信息,因為mybatis
的二級緩存區域以mapper
為單位劃分,當一個商品信息變化會將所有的商品信息的緩存數據全部清空。解決此類問題需要在業務層根據需求對數據有針對性緩存。