MyBatis緩存介紹

MyBatis緩存介紹

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

  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呢?java

  • spring整合mybatis後,非事務環境下,每次操做數據庫都使用新的sqlSession對象。所以mybatis的一級緩存沒法使用(一級緩存針對同一個sqlsession有效)redis

  • 在開啓事物的狀況之下,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,這樣說來是同一個對象。 會出現這種場景,第一次查出來的對象而後修改了,第二次查出來的就是修改後的對象。spring

一級緩存實現

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

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

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

配置二級緩存
  1. 在保證二級緩存的全局配置開啓的狀況下,給某個xml開啓二級緩存只須要在xml中添加便可
// 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。apache

<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的詳細配置能夠參考地址 www.ehcache.org/ehcache.xml 中的內容。

  • 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安裝啓動的相關內容,可參考以下地址中的官方文檔:redis.io/topics/quic… 目錄下新增 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 的緩存服務,就能夠將分佈式應用鏈接到同一個緩存服務器,實現分佈式應用間的緩存共享 。

相關文章
相關標籤/搜索