天天看點

MyBatis07:緩存

MyBatis07:緩存

簡介

1、什麼是緩存 [ Cache ]?

  • 存在記憶體中的臨時資料。
  • 将使用者經常查詢的資料放在緩存(記憶體)中,使用者去查詢資料就不用從磁盤上(關系型資料庫資料檔案)查詢,從緩存中查詢,進而提高查詢效率,解決了高并發系統的性能問題。

2、為什麼使用緩存?

  • 減少和資料庫的互動次數,減少系統開銷,提高系統效率。

3、什麼樣的資料能使用緩存?

  • 經常查詢并且不經常改變的資料。
Mybatis緩存
  • MyBatis包含一個非常強大的查詢緩存特性,它可以非常友善地定制和配置緩存。緩存可以極大的提升查詢效率。
  • MyBatis系統中預設定義了兩級緩存:一級緩存和二級緩存
    • 預設情況下,隻有一級緩存開啟。(SqlSession級别的緩存,也稱為本地緩存)
    • 二級緩存需要手動開啟和配置,他是基于namespace級别的緩存。
    • 為了提高擴充性,MyBatis定義了緩存接口Cache。我們可以通過實作Cache接口來自定義二級緩存
一級緩存

一級緩存也叫本地緩存:

  • 與資料庫同一次會話期間查詢到的資料會放在本地緩存中。
  • 以後如果需要擷取相同的資料,直接從緩存中拿,沒必須再去查詢資料庫;
測試

1、在mybatis中加入日志,友善測試結果

2、編寫接口方法

//根據id查詢使用者
User queryUserById(@Param("id") int id);      

3、接口對應的Mapper檔案

<select id="queryUserById" resultType="user">
  select * from user where id = #{id}
</select>      

4、測試

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);
   User user2 = mapper.queryUserById(1);
   System.out.println(user2);
   System.out.println(user==user2);

   session.close();
}      

5、結果分析

MyBatis07:緩存
一級緩存失效的四種情況

一級緩存是SqlSession級别的緩存,是一直開啟的,我們關閉不了它;

一級緩存失效情況:沒有使用到目前的一級緩存,效果就是,還需要再向資料庫中發起一次查詢請求!

1、sqlSession不同

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   SqlSession session2 = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);
   UserMapper mapper2 = session2.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);
   User user2 = mapper2.queryUserById(1);
   System.out.println(user2);
   System.out.println(user==user2);

   session.close();
   session2.close();
}      

觀察結果:發現發送了兩條SQL語句!

結論:每個sqlSession中的緩存互相獨立

2、sqlSession相同,查詢條件不同

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);
   UserMapper mapper2 = session.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);
   User user2 = mapper2.queryUserById(2);
   System.out.println(user2);
   System.out.println(user==user2);

   session.close();
}      

觀察結果:發現發送了兩條SQL語句!很正常的了解

結論:目前緩存中,不存在這個資料

3、sqlSession相同,兩次查詢之間執行了增删改操作!

增加方法

//修改使用者
int updateUser(Map map);      

編寫SQL

<update id="updateUser" parameterType="map">
  update user set name = #{name} where id = #{id}
</update>      
@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);

   HashMap map = new HashMap();
   map.put("name","kuangshen");
   map.put("id",4);
   mapper.updateUser(map);

   User user2 = mapper.queryUserById(1);
   System.out.println(user2);

   System.out.println(user==user2);

   session.close();
}      

觀察結果:查詢在中間執行了增删改操作後,重新執行了

結論:因為增删改操作可能會對目前資料産生影響

4、sqlSession相同,手動清除一級緩存

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);

   session.clearCache();//手動清除緩存

   User user2 = mapper.queryUserById(1);
   System.out.println(user2);

   System.out.println(user==user2);

   session.close();
}      

一級緩存就是一個map

二級緩存
  • 二級緩存也叫全局緩存,一級緩存作用域太低了,是以誕生了二級緩存
  • 基于namespace級别的緩存,一個名稱空間,對應一個二級緩存;
  • 工作機制
    • 一個會話查詢一條資料,這個資料就會被放在目前會話的一級緩存中;
    • 如果目前會話關閉了,這個會話對應的一級緩存就沒了;但是我們想要的是,會話關閉了,一級緩存中的資料被儲存到二級緩存中;
    • 新的會話查詢資訊,就可以從二級緩存中擷取内容;
    • 不同的mapper查出的資料會放在自己對應的緩存(map)中;
使用步驟

1、開啟全局緩存 【mybatis-config.xml】

<setting name="cacheEnabled" value="true"/>      

2、去每個mapper.xml中配置使用二級緩存,這個配置非常簡單;【xxxMapper.xml】

<cache/>

官方示例=====>檢視官方文檔
<cache
 eviction="FIFO"
 flushInterval="60000"
 size="512"
 readOnly="true"/>
這個更進階的配置建立了一個 FIFO 緩存,每隔 60 秒重新整理,最多可以存儲結果對象或清單的 512 個引用,而且傳回的對象被認為是隻讀的,是以對它們進行修改可能會在不同線程中的調用者産生沖突。      

3、代碼測試

  • 所有的實體類先實作序列化接口
  • 測試代碼
@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   SqlSession session2 = MybatisUtils.getSession();

   UserMapper mapper = session.getMapper(UserMapper.class);
   UserMapper mapper2 = session2.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);
   session.close();

   User user2 = mapper2.queryUserById(1);
   System.out.println(user2);
   System.out.println(user==user2);

   session2.close();
}      
結論
  • 隻要開啟了二級緩存,我們在同一個Mapper中的查詢,可以在二級緩存中拿到資料
  • 查出的資料都會被預設先放在一級緩存中
  • 隻有會話送出或者關閉以後,一級緩存中的資料才會轉到二級緩存中
緩存原理圖
MyBatis07:緩存
EhCache
MyBatis07:緩存

第三方緩存實作--EhCache: 檢視百度百科

Ehcache是一種廣泛使用的java分布式緩存,用于通用緩存;

要在應用程式中使用Ehcache,需要引入依賴的jar包

<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
   <groupId>org.mybatis.caches</groupId>
   <artifactId>mybatis-ehcache</artifactId>
   <version>1.1.0</version>
</dependency>      

在mapper.xml中使用對應的緩存即可

<mapper namespace = “org.acme.FooMapper” >
   <cache type = “org.mybatis.caches.ehcache.EhcacheCache” />
</mapper>      

編寫ehcache.xml檔案,如果在加載時未找到/ehcache.xml資源或出現問題,則将使用預設配置。

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
        updateCheck="false">
   <!--
      diskStore:為緩存路徑,ehcache分為記憶體和磁盤兩級,此屬性定義磁盤的緩存位置。參數解釋如下:
      user.home – 使用者主目錄
      user.dir – 使用者目前工作目錄
      java.io.tmpdir – 預設臨時檔案路徑
    -->
   <diskStore path="./tmpdir/Tmp_EhCache"/>
   
   <defaultCache
           eternal="false"
           maxElementsInMemory="10000"
           overflowToDisk="false"
           diskPersistent="false"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="259200"
           memoryStoreEvictionPolicy="LRU"/>

   <cache
           name="cloud_user"
           eternal="false"
           maxElementsInMemory="5000"
           overflowToDisk="false"
           diskPersistent="false"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="1800"
           memoryStoreEvictionPolicy="LRU"/>
   <!--
      defaultCache:預設緩存政策,當ehcache找不到定義的緩存時,則使用這個緩存政策。隻能定義一個。
    -->
   <!--
     name:緩存名稱。
     maxElementsInMemory:緩存最大數目
     maxElementsOnDisk:硬碟最大緩存個數。
     eternal:對象是否永久有效,一但設定了,timeout将不起作用。
     overflowToDisk:是否儲存到磁盤,當系統當機時
     timeToIdleSeconds:設定對象在失效前的允許閑置時間(機關:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,預設值是0,也就是可閑置時間無窮大。
     timeToLiveSeconds:設定對象在失效前允許存活時間(機關:秒)。最大時間介于建立時間和失效時間之間。僅當eternal=false對象不是永久有效時使用,預設是0.,也就是對象存活時間無窮大。
     diskPersistent:是否緩存虛拟機重新開機期資料 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
     diskSpoolBufferSizeMB:這個參數設定DiskStore(磁盤緩存)的緩存區大小。預設是30MB。每個Cache都應該有自己的一個緩沖區。
     diskExpiryThreadIntervalSeconds:磁盤失效線程運作時間間隔,預設是120秒。
     memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache将會根據指定的政策去清理記憶體。預設政策是LRU(最近最少使用)。你可以設定為FIFO(先進先出)或是LFU(較少使用)。
     clearOnFlush:記憶體數量最大時是否清除。
     memoryStoreEvictionPolicy:可選政策有:LRU(最近最少使用,預設政策)、FIFO(先進先出)、LFU(最少通路次數)。
     FIFO,first in first out,這個是大家最熟的,先進先出。
     LFU, Less Frequently Used,就是上面例子中使用的政策,直白一點就是講一直以來最少被使用的。如上面所講,緩存的元素有一個hit屬性,hit值最小的将會被清出緩存。
     LRU,Least Recently Used,最近最少使用的,緩存的元素有一個時間戳,當緩存容量滿了,而又需要騰出地方來緩存新的元素的時候,那麼現有緩存元素中時間戳離目前時間最遠的元素将被清出緩存。
  -->

</ehcache>      

合理的使用緩存,可以讓我們程式的性能大大提升!