MyBatis緩存介紹

MyBatis緩存介紹

  正如大多數持久層框架一樣,MyBatis 同樣提供了一級緩存和二級緩存的支持
  

  1. 一級緩存: 基於PerpetualCache 的 HashMap本地緩存,其存儲作用域爲 Session,當 Session flush 或 close 之後,該Session中的所有 Cache 就將清空。
  2. 二級緩存與一級緩存其機制相同,默認也是採用 PerpetualCache,HashMap存儲,不同在於其存儲作用域爲 Mapper(Namespace),並且可自定義存儲源,如 Ehcache。
  3. 對於緩存數據更新機制,當某一個作用域(一級緩存Session/二級緩存Namespaces)的進行了 C/U/D 操作後,默認該作用域下所有 select 中的緩存將被clear。
mybatis的相關概念
  1. SqlSession : 代表和數據庫的一次會話,向用戶提供了操作數據庫的方法。
  2. MappedStatement: 代表要發往數據庫執行的指令,可以理解爲是Sql的抽象表示。
  3. Executor: 具體用來和數據庫交互的執行器,接受MappedStatement作爲參數。
  4. 映射接口: 在接口中會要執行的Sql用一個方法來表示,具體的Sql寫在映射文件中。
  5. 映射文件: 可以理解爲是Mybatis編寫Sql的地方,通常來說每一張單表都會對應着一個映射文件,在該文件中會定義Sql語句入參和出參的形式。
一級緩存
  • MyBatis的一級查詢緩存(也叫作本地緩存)是基於org.apache.ibatis.cache.impl.PerpetualCache 類的 HashMap本地緩存,其作用域是SqlSession
  • 在同一個SqlSession中兩次執行相同的 sql 查詢語句,第一次執行完畢後,會將查詢結果寫入到緩存中,第二次會從緩存中直接獲取數據,而不再到數據庫中進行查詢,這樣就減少了數據庫的訪問,從而提高查詢效率。
  • 當一個 SqlSession 結束後,該 SqlSession 中的一級查詢緩存也就不存在了。
  • myBatis 默認一級查詢緩存是開啓狀態,且不能關閉。
  • 增刪改會清空緩存,無論是否commit

當SqlSession關閉和提交時,會清空一級緩存

可能你會有疑惑,我的mybatis bean是由spring 來管理的,已經屏蔽了sqlSession這個東西了?那怎麼的一次操作纔算是一次sqlSession呢?
  • spring整合mybatis後,非事務環境下,每次操作數據庫都使用新的sqlSession對象。因此mybatis的一級緩存無法使用(一級緩存針對同一個sqlsession有效)
  • 在開啓事物的情況之下,spring使用threadLocal獲取當前資源綁定同一個sqlSession,因此此時一級緩存是有效的

在開啓以及緩存的時候查詢得到的對象是同一個對象。
這種情況下會出現一個問題。我們先看一下代碼。

public void listMybatisModel() {
        List<MybatisModel> mybatisModels = mapper.listMybatisModel();
        List<MybatisModel> mybatisModelsOther = mapper.listMybatisModel();
        System.out.println(mybatisModels == mybatisModelsOther);
        System.out.println("list count: " + mybatisModels.size());
    }
System.out.println(mybatisModels == mybatisModelsOther);

輸出結果竟然是true,這樣說來是同一個對象。 會出現這種場景,第一次查出來的對象然後修改了,第二次查出來的就是修改後的對象。

一級緩存實現

對SqlSession的操作mybatis內部都是通過Executor來執行的。Executor的生命週期和SqlSession是一致的。Mybatis在Executor中創建了一級緩存,基於PerpetualCache 類的 HashMap

二級緩存
  • MyBatis的二級緩存是mapper範圍級別的
  • SqlSession關閉後纔會將數據寫到二級緩存區域
  • 增刪改操作,無論是否進行提交commit(),均會清空一級、二級緩存
  • 二級緩存是默認開啓的。(想開啓就不必做任何配置)
  • 二級緩存會使用 Least Recently Used (LRU,最近最少使用的)算法來收回。
  • 根據時間表(如 no Flush Interval ,沒有刷新間隔),緩存不會以任何時間順序來刷新 。
  • 緩存會存儲集合或對象(無論查詢方法返回什麼類型的值)的 1024 個引用。
  • 緩存會被視爲 read/write (可讀/可寫)的,意味着對象檢索不是共享的,而且可以安全地被調用者修改,而不干擾其他調用者或線程所做的潛在修改 。

用下面這張圖描述一級緩存和二級緩存的關係。

配置二級緩存
  1. 在保證二級緩存的全局配置開啓的情況下,給某個xml開啓二級緩存只需要在xml中添加<cache />即可
// mybatis-config.xml 中配置
<settings>
    默認值爲 true。即二級緩存默認是開啓的
    <setting name="cacheEnabled" value="true"/>
</settings>

// 具體mapper.xml 中配置
<mapper namespace="cn.itcast.mybatis.mapper.UserMapper">
    <!-- 開啓本mapper的namespace下的二級緩存
    type:指定cache接口的實現類的類型,mybatis默認使用PerpetualCache
    要和ehcache整合,需要配置type爲ehcache實現cache接口的類型-->

    <cache />

</mapper>

如果想要設置增刪改操作的時候不清空二級緩存的話,可以在其insert或delete或update中添加屬性flushCache=”false”,默認爲 true。

<delete id="deleteStudent" flushCache="false">
    DELETE FROM user where id=#{id}
</delete>

通過以下一些配置可以修改一些緩存參數。

<cache
    eviction= "FIFO"
    flushlnterval="600000"
    size="512"
    readOnly="true" />

配置創建了一個FIFO緩存,並每隔 60 秒刷新一次,存儲集合或對象的512個引用, 而且返回的對象被認爲是隻讀的,因此在不同線程中的調用者之間修改它們會導致衝突。cache可以配置的屬性如下。
eviction (收回策略)

LRU (最近最少使用的: 移除最長時間不被使用的對象,這是默認值 。
FIFO (先進先出〉 : 按對象進入緩存的順序來移除它們 。
SOFT (軟引用) : 移除基於垃圾回收器狀態和軟引用規則的對象 。
WEAK (弱引用) : 更積極地移除基於垃圾收集器狀態和弱引用規則的對象
  • flushinterval(刷新間隔)。

可以被設置爲任意的正整數,而且它們代表一個合理
的毫秒形式的時間段。默認情況不設置,即沒有刷新間隔,緩存僅僅在調用語句時刷新。

  • size (引用數目)。 可以被設置爲任意正整數,要記住緩存的對象數目和運行環境的可用內存資源數目。默認值是 1024 。
  • readOnly (只讀)。屬性可以被設置爲 true 或 false 。只讀的緩存會給所有調用者

返回緩存對象的相同實例,因此這些對象不能被修改,這提供了很重要的性能優勢。可讀寫的緩存會通過序列化返回緩存對象的拷貝,這種方式會慢一些,但是安全,因此默認是false。

  1. 當只使用註解方式配置二級緩存時,如果在Mapper接口中,則需要增加如下配置 。
@CacheNamespace (
eviction = FifoCache.class ,
flushinterval = 60000 ,
size = 512 ,
readWrite = true)
public interface Mapper {
    
}

括號內的內容是配置緩存屬性。

  1. Mapper 接口和對應的 XML 文件是相同的命名空間,想使用二級緩存,兩者必須同時配置(如果接口不存在使用註解方式的方法,可以只在 XML 中配置〉,因此按照上面的方

式進行配置就會出錯 , 這個時候應該使用參照緩存。在 Mapper 接口中,參照緩存配置如下 。

@CacheNarnespaceRef(RoleMapper.class)
public interface RoleMapper {

因爲想讓 RoleMapper 接口中的註解方法和 XML中的方法使用相同的緩存,因此使用參照緩存配置RoleMapper.class,這樣就會使用命名空間爲xx.xxx.xxx.xxx.RoleMapper的緩存配置,即RoleMapper.xml 中配置的緩存 。
Mapper 接口可以通過註解引用XML 映射文件或者其他接口的緩存,在 XML 中也可以配置參照緩存,如可以在 RoleMapper.xml 中進行如下修改 。

<cache-ref narnespace="xxx.xxx.xxx.xxx.RoleMapper"/>

這樣配置後XML 就會引用 Mapper 接口中配置的二級緩存,同樣可以避免同時配置二級緩存導致的衝突。MyBatis 中很少會同時使用 Mapper 接口註解方式和XML映射文件,所以參照緩存並不是爲了解決這個問題而設計的。參照緩存除了能夠通過引用其他緩存減少配置外,主要的作用是解決髒讀。

MyBatis使用SerializedCache(org.apache.ibaits.cache.decorators.SerializedCache)序列化緩存來實現可讀寫緩存類,井通過序列化和反序列化來保證通過緩存獲取數據時,得到的是一個新的實例。因此,如果配置爲只讀緩存,MyBatis就會使用Map來存儲緩存值,這種情況下,從緩存中獲取的對象就是同一個實例。因爲使用可讀寫緩存,可以使用SerializedCache序列化緩存。這個緩存類要求所有被序列化的對象必須實現 Serializable (java.io.Serializable)接口
雖然使用序列化得到的對象都是不一樣的對象修改時都是互不影響,但是還是不安全的。

髒讀的產生

Mybatis的二級緩存是和命名空間綁定的,所以通常情況下每一個Mapper映射文件都有自己的二級緩存,不同的mapper的二級緩存互不影響。

  • 引起髒讀的操作通常發生在多表關聯操作中,比如在兩個不同的mapper中都涉及到同一個表的增刪改查操作,當其中一個mapper對這張表進行查詢操作,此時另一個mapper進行了更新操作刷新緩存,然後第一個mapper又查詢了一次,那麼這次查詢出的數據是髒數據。出現髒讀的原因是他們的操作的緩存並不是同一個。
髒讀的避免
  • mapper中的操作以單表操作爲主,避免在關聯操作中使用mapper
  • 使用參照緩存
集成EhCache緩存

緩存數據有內存和磁盤兩級,無須擔心容量問題。

  • 緩存數據會在虛擬機重啓的過程中寫入磁盤。可以通過RMI、可插入API等方式進行分佈式緩存。
  • 具有緩存和緩存管理器的偵昕接口。
  • 支持多緩存管理器實例以及一個實例的多個緩存區域。
1. 添加項目依賴
    <dependency>
        <groupId>org.mybatis.caches</groupId>
        <artifactId>mybatis-ehcache</artifactId>
        <version>1.0.3</version>
    </dependency>
2. 配置 EhCache

在 src/main/resources 目錄下新增 ehcache.xml 文件。

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="ehcache.xsd"
    updateCheck="false" monitoring="autodetect"
    dynamicConfig="true">
    
    <diskStore path="D:/cache" />
            
    <defaultCache      
        maxElementsInMemory="3000"      
        eternal="false"      
        copyOnRead="true"
        copyOnWrite="true"
        timeToIdleSeconds="3600"      
        timeToLiveSeconds="3600"      
        overflowToDisk="true"      
        diskPersistent="true"/> 
</ehcache>

有關EhCache的詳細配置可以參考地址 http://www.ehcache.org/ehcach... 中的內容。

  • copyOnRead 的含義是,判斷從緩存中讀取數據時是返回對象的引用還是複製一個對象返回。默認情況下是false,即返回數據的引用,這種情況下返回的都是相同的對象,和MyBatis默認緩存中的只讀對象是相同的。如果設置爲 true ,那就是可讀寫緩存,每次讀取緩存時都會複製一個新的實例 。
  • copyOnWrite 的含義是 ,判斷寫入緩存時是直接緩存對象的引用還是複製一個對象然後緩存,默認也是false。如果想使用可讀寫緩存,就需要將這兩個屬性配置爲true,如果使用只讀緩存,可以不配置這兩個屬性,使用默認值 false 即可 。
  1. 修改Mapper.xml中的緩存配置

ehcache-cache 提供瞭如下 2 個可選的緩存實現。

  • org.mybatis.caches.ehcache.EhcacheCache
  • org.mybatis.caches.ehcache.LoggingEhcache 這個是帶日誌的緩存。

在xml中添加

 <cache type ="org.mybatis.caches.ehcache.EhcacheCache" />

只通過設置 type 屬性就可 以使用 EhCache 緩存了,這時cache的其他屬性都不會起到任何作用,針對緩存的配置都在ehcache.xml中進行。在ehcache.xml配置文件中,只有一個默認的緩存配置,所以配置使用EhCache緩存的Mapper映射文件都會有一個以映射文件命名空間命名的緩存。如果想針對某一個命名空間進行配置,需要在 ehcache.xml 中添加一個和映射文件命名空間一致的緩存配置,例如針對RoleMapper可以進行如下配置。

    <cache      
        name="tk.mybatis.simple.mapper.RoleMapper"
        maxElementsInMemory="3000"      
        eternal="false"      
        copyOnRead="true"
        copyOnWrite="true"
        timeToIdleSeconds="3600"      
        timeToLiveSeconds="3600"      
        overflowToDisk="true"      
        diskPersistent="true"/>
集成Redis緩存
  1. 添加依賴,目前只有bata版本。
        <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-redis</artifactId>
            <version>1.0.0-beta2</version>
        </dependency>
  1. 配置Redis

使用 Redis 前,必須有一個 Redis 服務,有關Redis安裝啓動的相關內容,可參考如下地址中的官方文檔:https://redis.io/topics/quick...。Redis服務啓動後,在src/main/resources 目錄下新增 redis.properties 文件 。

host=localhost
port=6379
connectionTimeout=SOOO
soTimeout=SOOO
password=
database=O
clientName=
  1. 修改mapper.xml中的配置。
<mapper namespace = ” tk.mybat 工 s.s 工 mple.mapper.RoleMapper ” 〉
<cache type= "org.mybatis.caches.redis.RedisCache" />
〈 !一其他自己直一 〉
</mapper>

配置依然很簡單, RedisCache 在保存緩存數據和獲取緩存數據時,使用了Java的序列化和反序列化,因此還需要保證被緩存的對象必須實現Serializable接口。改爲RedisCache緩存配置後, testL2Cache 測試第一次執行時會全部成功,但是如果再次執行,就會出錯。這是因爲Redis作爲緩存服務器,它緩存的數據和程序(或測試)的啓動無關,Redis 的緩存並不會因爲應用的關閉而失效。所以再次執行時沒有進行一次數據庫查詢,所有查詢都使用緩存,測試的第一部分代碼中的rolel和role2都是直接從二級緩存中獲取數據,因爲是可讀寫緩存,所以不是相同的對象。當需要分佈式部署應用時,如果使用MyBatis自帶緩存或基礎的EhCahca緩存,分佈式應用會各自擁有自己的緩存,它們之間不會共享緩存 ,這種方式會消耗更多的服務器資源。如果使用類似 Redis 的緩存服務,就可以將分佈式應用連接到同一個緩存服務器,實現分佈式應用間的緩存共享 。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章