Java Cache 入門(mén)

什么是緩存

術(shù)語(yǔ)緩存在計(jì)算機(jī)中無(wú)處不在撞蜂。在應(yīng)用程序設(shè)計(jì)的上下文中赡若,它經(jīng)常被用來(lái)描述應(yīng)用程序開(kāi)發(fā)人員利用單獨(dú)的內(nèi)存或低延遲的數(shù)據(jù)結(jié)構(gòu)。緩存宏悦,用于臨時(shí)存儲(chǔ)或緩存信息的副本或引用,應(yīng)用程序可能會(huì)在稍后的某個(gè)時(shí)間點(diǎn)重復(fù)使用包吝,從而減輕重新訪問(wèn)或重新創(chuàng)建它的成本饼煞。

在 JCache 的上下文中,術(shù)語(yǔ)緩存描述了 Java 的技術(shù)開(kāi)發(fā)人員使用緩存提供程序臨時(shí)緩存 Java 對(duì)象漏策。

JSR107(JCache)

JCache 是 Java 的緩存 API派哲。它由 JSR107 定義。它定義了供開(kāi)發(fā)人員使用的標(biāo)準(zhǔn) Java 緩存 API 和供實(shí)現(xiàn)者使用的標(biāo)準(zhǔn) SPI(“服務(wù)提供者接口”)掺喻。

它為 Java 提供了通用的方法來(lái)創(chuàng)建芭届,訪問(wèn),更新和刪除緩存中的條目(entries)感耙。

標(biāo)準(zhǔn)定義文檔

JCache 核心概念

Java 的緩存 API 定義了五個(gè)核心接口:CachingProvider褂乍,CacheManagerCache即硼,EntryExpiryPolicy逃片。

CachingProvider 定義了建立,配置只酥,獲取褥实,管理和控制零個(gè)或多個(gè) CacheManager 的機(jī)制。應(yīng)用程序可以在運(yùn)行期間訪問(wèn)和使用零個(gè)或多個(gè) CachingProvider裂允。

CacheManager 定義在上下文中了建立损离,配置,獲取绝编,管理和控制零個(gè)或多個(gè)唯一命名的緩存的機(jī)制僻澎。 CacheManager 被單個(gè) CachingProvider 擁有。

Cache 是一個(gè)像 Map 一樣的數(shù)據(jù)結(jié)構(gòu)十饥,它允許基于 Key 的臨時(shí)儲(chǔ)存窟勃。緩存被單個(gè) CacheManager 擁有。

Entry 是被 Cache 存儲(chǔ)的單個(gè) key-value 對(duì)逗堵,JCache 允許我們定義按值或按引用來(lái)存儲(chǔ)條目秉氧。

每一個(gè)被 Cache 存儲(chǔ)的 entry 都定義了持續(xù)時(shí)間,被稱(chēng)作過(guò)期持續(xù)時(shí)間砸捏,在這個(gè)時(shí)間內(nèi)他們可以訪問(wèn)谬运,更新和刪除隙赁。一旦這個(gè)過(guò)期時(shí)間過(guò)去垦藏,該條目就被認(rèn)為是過(guò)期了梆暖。一旦過(guò)期,就不能再訪問(wèn)掂骏,更新和刪除條目轰驳,就像他們從未在緩存中存在。過(guò)期是使用 ExpiryPolicy 來(lái)設(shè)置的弟灼。

JCache 還為我們定義了緩存條目監(jiān)聽(tīng)器的接口级解,我們可以注冊(cè)條目不同事件的監(jiān)聽(tīng)器在運(yùn)行期或配置階段,通過(guò) CacheEntryListenerConfiguration 注冊(cè) CacheEntryListener田绑。

我們也可以配置啟用和禁用管理和統(tǒng)計(jì)信息通過(guò)使用 MXBean:

  • setManagementEnabled(boolean enabled) 啟用管理
  • setStatisticsEnabled(boolean enabled) 啟用統(tǒng)計(jì)

按值存儲(chǔ)和按引用存儲(chǔ)

條目由每個(gè)緩存使用兩種機(jī)制之一存儲(chǔ)勤哗。

  • 默認(rèn)的機(jī)制,稱(chēng)為值存儲(chǔ)掩驱。指示實(shí)現(xiàn)使副本應(yīng)用程序在將它們儲(chǔ)存在 Cache 中之前提供鍵和值芒划,之后在訪問(wèn)緩存時(shí)再返回新的條目副本。復(fù)制條目的目的是存儲(chǔ)在緩存和從緩存中再次返回時(shí)應(yīng)用程序繼續(xù)變異鍵和值的狀態(tài)欧穴,不會(huì)對(duì)緩存持有的條目造成副作用民逼。

    一個(gè)簡(jiǎn)單的方法實(shí)現(xiàn)可用于制作鍵和值的副本是 Java 序列化。

  • 替代和可選機(jī)制涮帘,稱(chēng)為引用存儲(chǔ)拼苍。指示 Cache 實(shí)現(xiàn)簡(jiǎn)單地存儲(chǔ)和返回對(duì)應(yīng)用程序提供的鍵和值的引用,而不是按照按值存儲(chǔ)方法的要求制作副本调缨。如果應(yīng)用程序稍后使用引用存儲(chǔ)語(yǔ)義來(lái)改變提供給緩存的鍵或值疮鲫,則突變的副作用對(duì)于從緩存訪問(wèn)條目的人來(lái)說(shuō)是可見(jiàn)的,而應(yīng)用程序不必更新緩存弦叶。

    對(duì)于在 Java 堆上實(shí)現(xiàn)的緩存俊犯,引用存儲(chǔ)是最快的存儲(chǔ)技術(shù)。

從 JCache 到 EhCache

使用 JCache 可以完成大部分的緩存操作湾蔓,但是如果需要使用到 EhCache 的特性瘫析,則必須使用提供給程序的特定 API。比如需要用到持久化目錄等默责。下圖是 EhCache 的架構(gòu)圖:

Image result for ehcache architecture diagram

使用 EhCache 作為 JCache 供應(yīng)商

JCache 作為標(biāo)準(zhǔn)有很多的實(shí)現(xiàn)贬循,我們這里主要以 EhCache 來(lái)展開(kāi)學(xué)習(xí),要想使用 EhCache 作為 JCache 的供應(yīng)商很簡(jiǎn)單桃序,我們只要將 EhCache 的 jar 包和 JCache 放在一起便可以使用杖虾。

通過(guò) JCache 的接口,我們便可以操控緩存:

CachingProvider provider = Caching.getCachingProvider();  
CacheManager cacheManager = provider.getCacheManager();   
MutableConfiguration<Long, String> configuration =
    new MutableConfiguration<Long, String>()  
        .setTypes(Long.class, String.class)   
        .setStoreByValue(false)   
        .setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(Duration.ONE_MINUTE));  
Cache<Long, String> cache = cacheManager.createCache("jCache", configuration); 
cache.put(1L, "one"); 
String value = cache.get(1L); 

JCache 會(huì)從應(yīng)用程序的類(lèi)路徑中檢索默認(rèn)的 CachingProvider 實(shí)現(xiàn)媒熊。但這必須保證類(lèi)路徑上只有一個(gè) JCache 實(shí)現(xiàn)奇适,否則必須指定具體的供應(yīng)商名稱(chēng)坟比,可以使用設(shè)置系統(tǒng)變量 javax.cache.spi.CachingProvider 或通過(guò)調(diào)用 Caching.getCachingProvider(String) 靜態(tài)方法指定完整的類(lèi)名稱(chēng)。

上述代碼中嚷往,我們獲取到緩存的供應(yīng)器葛账,并從中得到一個(gè)默認(rèn)的 CacheManager 實(shí)例,使用不可用的配置類(lèi)設(shè)置了鍵和值的類(lèi)型皮仁,條目的存儲(chǔ)類(lèi)型籍琳,以及過(guò)期方案來(lái)生成一個(gè)名稱(chēng)為 JCache 的緩存對(duì)象,并向緩存中添加了一組數(shù)據(jù)贷祈,并使用鍵獲取值趋急。

JCache 提供了一組最小配置,非常適合內(nèi)存緩存势誊。但是 EhCache 在拓?fù)浜凸δ芊矫嫣峁┝烁鼜?qiáng)大的實(shí)現(xiàn)呜达。

從 JCache 配置訪問(wèn)基礎(chǔ) Ehcache 配置

當(dāng)以使用 JCache 創(chuàng)建了配置時(shí),你任然可以訪問(wèn)底層的 EhCache CacheRuntimeConfiguration粟耻。

MutableConfiguration<Long, String> configuration = new MutableConfiguration<>();
configuration.setTypes(Long.class, String.class);
Cache<Long, String> cache = cacheManager.createCache("someCache", configuration); 

CompleteConfiguration<Long, String> completeConfiguration = cache.getConfiguration(CompleteConfiguration.class); 

Eh107Configuration<Long, String> eh107Configuration = cache.getConfiguration(Eh107Configuration.class); 

CacheRuntimeConfiguration<Long, String> runtimeConfiguration = eh107Configuration.unwrap(CacheRuntimeConfiguration.class);

我們還可以使用 Ehcache CacheConfiguration 創(chuàng)建 JCache 緩存查近。

EhCache 不總是同意 JCache 的默認(rèn)行為。兩者在默認(rèn)值上存在一定的差異勋颖。

使用編程的方式配置 EhCache

我們可以使用編程配置或 XML 來(lái)配置 EhCache嗦嗡,個(gè)人比較喜歡以編程的形式配置大多數(shù)不是很容易變動(dòng)的配置,通過(guò)使用提供流暢 API 的構(gòu)建器我們可以很容易完成配置饭玲。

CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder() 
    .withCache("preConfigured",
        CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10))) 
    .build(); 
cacheManager.init(); 

Cache<Long, String> preConfigured =
    cacheManager.getCache("preConfigured", Long.class, String.class); 

Cache<Long, String> myCache = cacheManager.createCache("myCache", 
    CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10)));

myCache.put(1L, "da one!"); 
String value = myCache.get(1L); 

cacheManager.removeCache("preConfigured"); 

cacheManager.close(); 

這里的配置很清晰明了侥祭,具體配置我們可以查看配置參數(shù)查看。

EhCache 存儲(chǔ)層

我們可以配置 Ehcache 以使用各種數(shù)據(jù)存儲(chǔ)區(qū)域茄厘。當(dāng)緩存配置為使用多個(gè)存儲(chǔ)區(qū)域時(shí)矮冬,這些區(qū)域?qū)磳优帕泻凸芾怼K鼈儼磳哟谓Y(jié)構(gòu)組織次哈,最低層(更遠(yuǎn))稱(chēng)為權(quán)限層胎署,其他層則是緩存層的一部分(更近,也稱(chēng)為近緩存)窑滞。緩存層本身可以由多個(gè)存儲(chǔ)區(qū)域組成琼牧。最熱門(mén)的數(shù)據(jù)保存在緩存層中,這通常不如權(quán)限級(jí)別那么豐富但速度更快哀卫。所有數(shù)據(jù)都保存在權(quán)限層中巨坊,這更慢但更豐富。

Ehcache 支持的數(shù)據(jù)存儲(chǔ)包括:

  • 堆內(nèi)存儲(chǔ) - 利用 Java 的堆內(nèi) RAM 內(nèi)存來(lái)存儲(chǔ)緩存條目此改。此層使用與 Java 應(yīng)用程序相同的堆內(nèi)存趾撵,所有這些都必須由 JVM 垃圾收集器掃描。JVM 使用的堆空間越多共啃,應(yīng)用程序性能就越會(huì)受到垃圾收集暫停的影響占调。這個(gè)存儲(chǔ)非吃萏猓快,但通常是您最有限的存儲(chǔ)資源究珊。

  • 堆外存儲(chǔ) - 大小受限于可用的 RAM 內(nèi)存薪者。不受 Java 垃圾收集(GC)的限制。速度非晨嘁快啸胧,但比堆內(nèi)存儲(chǔ)慢赶站,因?yàn)閿?shù)據(jù)必須在存儲(chǔ)和重新訪問(wèn)時(shí)移入和移出 JVM 堆幔虏。

  • 磁盤(pán)存儲(chǔ) - 利用磁盤(pán)(文件系統(tǒng))來(lái)存儲(chǔ)緩存條目。這種類(lèi)型的存儲(chǔ)資源通常非常豐富贝椿,但比基于 RAM 的存儲(chǔ)要慢得多想括。對(duì)于使用磁盤(pán)存儲(chǔ)的所有應(yīng)用程序,建議使用快速專(zhuān)用磁盤(pán)來(lái)優(yōu)化吞吐量烙博。

  • 集群存儲(chǔ) - 此數(shù)據(jù)存儲(chǔ)是遠(yuǎn)程服務(wù)器上的緩存瑟蜈。遠(yuǎn)程服務(wù)器可以可選地提供具有高可用性的故障轉(zhuǎn)移服務(wù)器。由于集群存儲(chǔ)會(huì)因網(wǎng)絡(luò)延遲以及建立客戶端/服務(wù)器一致性等因素而導(dǎo)致性能下降渣窜,這個(gè)層本質(zhì)上比本地堆外存儲(chǔ)慢铺根。

    EhcacheTerminology

用戶管理的緩存

用戶管理的緩存為我們提供了一種直接配置緩存的簡(jiǎn)單方法,而無(wú)需設(shè)置或使用 CacheManager 的復(fù)雜性乔宿。是否使用 UserManagedCache 而不是 CacheManager 的選擇通常取決于我們是否需要 CacheManager 的所有內(nèi)置功能位迂。如果緩存要求相對(duì)簡(jiǎn)單,并且不需要CacheManager的全部功能详瑞,則可以考慮使用UserManagedCache掂林。

使用 UserManagedCache 的典型方案有:方法本地緩存,線程本地緩存或緩存生命周期短于應(yīng)用程序生命周期的任何其他位置坝橡。

使用介紹

Hibernate 緩存

數(shù)據(jù)庫(kù)抽象層(如ORM(對(duì)象 - 關(guān)系映射)框架)的一個(gè)優(yōu)點(diǎn)是它們能夠透明地緩存從底層存儲(chǔ)中檢索的數(shù)據(jù)泻帮。這有助于消除頻繁訪問(wèn)數(shù)據(jù)的數(shù)據(jù)庫(kù)訪問(wèn)成本。

在運(yùn)行時(shí)计寇,Hibernate處理將數(shù)據(jù)移入和移出二級(jí)緩存以響應(yīng) Session 執(zhí)行的操作锣杂,該操作充當(dāng)持久數(shù)據(jù)的事務(wù)級(jí)緩存。一旦實(shí)體被管理番宁,該對(duì)象就會(huì)被添加到當(dāng)前持久化上下文(EntityManager 或 Session)的內(nèi)部緩存中元莫。持久性上下文也稱(chēng)為第一級(jí)緩存,默認(rèn)情況下已啟用贝淤。

可以在 class-by-class 逐個(gè)類(lèi)和 collection-by-collection 逐個(gè)集合的基礎(chǔ)上配置 JVM 級(jí)別(SessionFactory 級(jí)別)甚至是集群緩存柒竞,二級(jí)緩存是 SessionFactory 域的,意味著它由使用相同會(huì)話工廠創(chuàng)建的所有會(huì)話共享播聪。

Hibernate 緩存不了解其他應(yīng)用程序?qū)Τ志眯源鎯?chǔ)所做的更改朽基。比如 EhCache 管理的緩存布隔。要解決此限制,可以在二級(jí)緩存區(qū)域級(jí)別配置 TTL(生存時(shí)間)保留策略稼虎,以便底層緩存條目定期到期衅檀。

Hibernate 可以與各種緩存提供程序集成,以便在特定會(huì)話的上下文之外緩存數(shù)據(jù)霎俩。

Hibernate 二級(jí)緩存條目默認(rèn)是使用哪種鍵存儲(chǔ)方式呢哀军?

StrategySelectorBuilder 策略選擇器構(gòu)建器,添加了兩個(gè)默認(rèn)的緩存鍵工廠類(lèi)的策略實(shí)現(xiàn)打却。

private void addCacheKeysFactories(StrategySelectorImpl strategySelector) {
   strategySelector.registerStrategyImplementor(
      CacheKeysFactory.class,
      DefaultCacheKeysFactory.SHORT_NAME,
      DefaultCacheKeysFactory.class
   );
   strategySelector.registerStrategyImplementor(
      CacheKeysFactory.class,
      SimpleCacheKeysFactory.SHORT_NAME,
      SimpleCacheKeysFactory.class
   );
}

在第一個(gè) “default” 工廠類(lèi)我們可以看到杉适,它為實(shí)體,集合都是使用 CacheKeyImplementation 來(lái)生成鍵柳击,也就是使用值存儲(chǔ)猿推,在放入前生成鍵和值。

第二個(gè) “simple” 工廠類(lèi)在為對(duì)象和集合生成鍵時(shí)直接引用傳遞過(guò)來(lái)的對(duì)象捌肴,也就是使用引用存儲(chǔ)的形式蹬叭。

RegionFactory

org.hibernate.cache.spi.RegionFactory 定義了 Hibernate 與可插拔緩存提供程序之間的集成。Hibernate 二級(jí)緩存設(shè)計(jì)為讓我們感覺(jué)不到所使用的實(shí)際緩存状知,SPI(服務(wù)提供者接口)為緩存實(shí)現(xiàn)者定義了接口秽五,Hibernate 只需要提供 org.hibernate.cache.spi.RegionFactory 接口的實(shí)現(xiàn),該接口封裝了特定于實(shí)際緩存提供者的所有細(xì)節(jié)饥悴√勾基本上,它充當(dāng) Hibernate 和緩存提供者之間的橋梁铺坞。Hibernate 內(nèi)置支持 Java 緩存標(biāo)準(zhǔn) JCache 以及兩個(gè)流行的緩存庫(kù):EhcacheInfinispan起宽。

  • 我們可以通過(guò)定義 hibernate.cache.region.factory_class 來(lái)指定具體提供商。

  • hibernate 還提供了其他的配置济榨,比如是否啟用二級(jí)緩存坯沪,hibernate.cache.use_second_level_cache,默認(rèn)二級(jí)緩存是關(guān)閉的擒滑,如果配置了 RegionFacoty 且不是 NoCachingRegionFactory時(shí)腐晾,二級(jí)緩存默認(rèn)啟用。

  • hibernate.cache.use_query_cache:是否啟用查詢緩存丐一,默認(rèn)關(guān)閉藻糖。

  • hibernate.cache.query_cache_factory:查詢結(jié)果緩存由特殊的規(guī)則處理,該規(guī)則定義了數(shù)據(jù)的實(shí)效策略库车。默認(rèn)實(shí)現(xiàn)是不失效巨柒,適合用于對(duì)數(shù)據(jù)要求松散的應(yīng)用。或者我們可以指定自己的規(guī)則洋满。

  • hibernate.cache.auto_evict_collection_cache:當(dāng)關(guān)聯(lián)僅從擁有方改變時(shí)晶乔,啟用或禁用雙向關(guān)聯(lián)的集合緩存條目的自動(dòng)驅(qū)逐。默認(rèn)情況下禁用該選項(xiàng)牺勾,因?yàn)樗鼤?huì)跟蹤此狀態(tài)對(duì)性能產(chǎn)生影響正罢。但是,如果您的應(yīng)用程序不管理高速緩存集合端的雙向關(guān)聯(lián)的雙方驻民,則替代方法是在該集合緩存中使用陳舊數(shù)據(jù)翻具。

    使用陳舊數(shù)據(jù)時(shí)很可能會(huì)出現(xiàn)數(shù)據(jù)不一致的情況,比如實(shí)體存在于緩存中但不存在于數(shù)據(jù)庫(kù)中回还,在做級(jí)聯(lián)刪除時(shí)裆泳,會(huì)報(bào) EntityNotFoundException 異常,可以在級(jí)聯(lián)刪除的集合上配置 @NotFound(action = NotFoundAction.IGNORE) 忽略該異常懦趋。

  • hibernate.cache.use_reference_entries:允許將實(shí)體引用直接存儲(chǔ)到二級(jí)緩存中晾虑,以用于只讀或不可變實(shí)體。使用引用存儲(chǔ)緩存的好處是我們不用顯式的維護(hù)緩存仅叫,緩存更新時(shí)直接反映給調(diào)用者。缺點(diǎn)是該緩存只可用于只讀狀態(tài)糙捺,否則對(duì)象逸出诫咱,會(huì)很難控制。

配置二級(jí)緩存映射

可以通過(guò) JPA 注解或 XML 描述符或使用特定于 Hibernate 的映射文件來(lái)配置緩存映射洪灯。默認(rèn)情況下坎缭,實(shí)體不是二級(jí)緩存的一部分,我們可以通過(guò)配置 javax.persistence.sharedCache.mode 來(lái)映射緩存签钩。該配置有四個(gè)選項(xiàng):

  • ENABLE_SELECTIVE:默認(rèn)也是推薦的選項(xiàng)掏呼。除非明確標(biāo)記為可緩存(使用 @Cacheable 注解),否則不會(huì)緩存實(shí)體铅檩。
  • DISABLE_SELECTIVE:除非明確標(biāo)記為不可緩存 @Cachable 注解 value 設(shè)置為 false憎夷,否則實(shí)體將被緩存。
  • ALL:即使標(biāo)記為不可緩存昧旨,實(shí)體也始終被緩存拾给。
  • NONE:即使標(biāo)記為可緩存,也不會(huì)緩存任何實(shí)體兔沃。此選項(xiàng)可以完全禁用二級(jí)緩存蒋得。

默認(rèn)情況下使用的緩存并發(fā)策略可以通過(guò)以下方式全局設(shè)置 hibernate.cache.default_cache_concurrency_strategy 來(lái)配置屬性,配置選項(xiàng)如下:

  • read-only:如果您的應(yīng)用程序需要讀取但不修改持久化類(lèi)的實(shí)例乒疏,則只讀緩存是最佳選擇额衙。應(yīng)用程序仍然可以刪除實(shí)體,這些更改應(yīng)該反映在二級(jí)緩存中,以便緩存不提供過(guò)時(shí)的實(shí)體窍侧。實(shí)現(xiàn)可以基于實(shí)體的不變性來(lái)使用性能優(yōu)化追驴。

  • read-write:如果應(yīng)用程序需要更新數(shù)據(jù),則讀寫(xiě)緩存可能是合適的疏之。此策略提供對(duì)單個(gè)實(shí)體的一致訪問(wèn)殿雪,但不提供可序列化的事務(wù)隔離級(jí)別;實(shí)現(xiàn)可以基于實(shí)體的不變性來(lái)使用性能優(yōu)化锋爪。

  • nonstrict-read-write:與讀寫(xiě)策略類(lèi)似丙曙,但在并發(fā)訪問(wèn)實(shí)體時(shí)可能偶爾會(huì)出現(xiàn)過(guò)時(shí)讀取。如果應(yīng)用程序很少同時(shí)更新相同的數(shù)據(jù)并且不需要嚴(yán)格的事務(wù)隔離其骄,則可以選擇此策略亏镰。實(shí)現(xiàn)可以使用利用寬松一致性保證的性能優(yōu)化。

  • transactional:提供可序列化的事務(wù)隔離級(jí)別拯爽。

建議不要使用全局設(shè)置索抓,而是基于每個(gè)實(shí)體定義緩存并發(fā)策略。為此毯炮,請(qǐng)使用 @ org.hibernate.annotations.Cache 注解逼肯。

該注解有三個(gè)屬性:

  • usage:定義 CacheConcurrencyStrategy。
  • region:定義存儲(chǔ)條目的緩存區(qū)域桃煎。對(duì)于每個(gè)實(shí)體類(lèi)篮幢,Hibernate 將使用單獨(dú)的緩存區(qū)域來(lái)存儲(chǔ)該類(lèi)的實(shí)例狀態(tài)。區(qū)域名稱(chēng)是完全限定的類(lèi)名为迈。
  • include:如果(lazy)延遲屬性應(yīng)包含在二級(jí)緩存中三椿。默認(rèn)值是所有延遲屬性都可緩存。另一個(gè)可能的值是非延遲的葫辐,因此延遲屬性不可緩存搜锰。

實(shí)體繼承和二級(jí)緩存映射

傳統(tǒng)上,當(dāng)使用實(shí)體繼承時(shí)耿战,Hibernate 要求實(shí)體層次結(jié)構(gòu)完全緩存或根本不緩存蛋叼。因此,如果要緩存屬于給定實(shí)體層次結(jié)構(gòu)的子類(lèi)昆箕,則 JPA @Cacheable 和特定于 Hibernate 的 @Cache 注解必須僅在根實(shí)體級(jí)別聲明鸦列。

Cacheable 注解的值由子類(lèi)繼承;可以通過(guò)在子類(lèi)上指定 Cacheable 來(lái)覆蓋它鹏倘。從 Hibernate ORM 5.3 開(kāi)始薯嗤,您現(xiàn)在可以在子類(lèi)級(jí)別覆蓋基類(lèi) @Cacheable 或 @Cache 定義。但是纤泵,Hibernate 緩存并發(fā)策略(例如骆姐,只讀镜粤,非嚴(yán)格讀寫(xiě),讀寫(xiě)玻褪,事務(wù))仍然在根實(shí)體級(jí)別定義肉渴,并且不能被覆蓋。

管理緩存數(shù)據(jù)

傳統(tǒng)上带射,Hibernate 定義了 CacheMode 枚舉來(lái)描述與緩存數(shù)據(jù)交互的方式同规。 JPA 通過(guò)存儲(chǔ)(CacheStoreMode)和檢索(CacheRetrieveMode)分割緩存模式。兩者的關(guān)系如下表:

Hibernate JPA 描述
CacheMode.NORMAL CacheStoreMode.USECacheRetrieveMode.USE 默認(rèn)窟社。from/into 緩存讀取/寫(xiě)入數(shù)據(jù)
CacheMode.REFRESH CacheStoreMode.REFRESH` 和 `CacheRetrieveMode.BYPASS 不從緩存中讀取券勺,而是在從數(shù)據(jù)庫(kù)加載時(shí)寫(xiě)入緩存
CacheMode.PUT CacheStoreMode.USECacheRetrieveMode.BYPASS 不從緩存中讀取,而是在從數(shù)據(jù)庫(kù)讀取時(shí)寫(xiě)入緩存
CacheMode.GET CacheStoreMode.BYPASSCacheRetrieveMode.USE 從緩存中讀取灿里,但不寫(xiě)入緩存
CacheMode.IGNORE CacheStoreMode.BYPASSCacheRetrieveMode.BYPASS 不從緩存讀取/寫(xiě)入數(shù)據(jù)

設(shè)置緩存模式可以在直接加載實(shí)體或執(zhí)行查詢時(shí)完成关炼。

  • 使用 JPA 自定義緩存模式:
Map<String, Object> hints = new HashMap<>(  );
hints.put( "javax.persistence.cache.retrieveMode" , CacheRetrieveMode.USE );
hints.put( "javax.persistence.cache.storeMode" , CacheStoreMode.REFRESH );
Person person = entityManager.find( Person.class, 1L , hints);
  • 使用 Hibernate 自定義緩存模式:
session.setCacheMode( CacheMode.REFRESH );
Person person = session.get( Person.class, 1L );
  • 使用 Spring Data JPA 自定義緩存模式(1.10 版本之后):
@QueryHints(value = {
    @QueryHint(name = "javax.persistence.cache.retrieveMode", value = "USE"),
    @QueryHint(name = "javax.persistence.cache.storeMode", value = "REFRESH")
})
Optional<Person> findById(Long id);

查詢緩存,集合緩存匣吊,和緩存統(tǒng)計(jì)以后再來(lái)補(bǔ)充儒拂。

Spring Boot 使用 EhCache

個(gè)人使用 Gradle 管理項(xiàng)目依賴,以下所述依賴皆是以基于Groovy 的特定于域的語(yǔ)言(DSL)定義色鸳。

僅使用緩存

Spring Boot 使用緩存非常簡(jiǎn)單社痛,我們只需要導(dǎo)入所需要的包即可開(kāi)箱即用,如果我們僅僅想使用緩存缕碎,則直接引入:org.springframework.boot:spring-boot-starter-cache starter 便可使用褥影。

在配置類(lèi)或啟動(dòng)類(lèi)上加入 @EnableCaching 注解,該注解會(huì)觸發(fā)一個(gè)后處理器(post processor)去檢測(cè)每個(gè) Spring Bean 上是否存在公共方法的緩存注解咏雌。如果找到這樣的注解,則自動(dòng)創(chuàng)建代理以攔截方法調(diào)用并相應(yīng)地處理緩存行為校焦。

此后處理器管理的注釋是 Cacheable赊抖,CachePutCacheEvict。Spring Boot 會(huì)自動(dòng)配置合適的CacheManager 作為相關(guān)緩存的提供程序寨典。如果只引入了該包氛雪,則默認(rèn)只會(huì)使用 Spring 上下文 ConcurrentHashMap 結(jié)構(gòu)來(lái)存儲(chǔ)緩存,這完全符合 JCache 的標(biāo)準(zhǔn)耸成。

如果當(dāng)前上下文中存在 JSR-107 API报亩,即 javax.cache:cache-api 該 jar 包,將額外的為 JSR-107 API 注解的 bean 創(chuàng)建代理井氢,這些注解是 @CacheResult弦追,@CachePut@CacheRemove@CacheRemoveAll花竞。

使用 Hibernate JCache 標(biāo)準(zhǔn)來(lái)構(gòu)建二級(jí)緩存

要使用 JCache 的內(nèi)置集成劲件,您需要將 org.hibernate:hibernate-jcache jar 集成進(jìn)去。此外世剖,還需要添加 JCache 實(shí)現(xiàn)吩跋。

hibernate-jcache 模塊定義了以下區(qū)域工廠:JCacheRegionFactory

要使用該 JCacheRegionFactory弊攘,只需配置該屬性為:hibernate.cache.region.factory_class: org.hibernate.cache.jcache.JCacheRegionFactory牵辣。

JCache 要求共享相同 URI 和類(lèi)加載器的 CacheManagers 在 JVM 中是唯一的摔癣。如果未指定其他屬性,JCacheRegionFactory 將加載默認(rèn)的 JCache 提供程序并創(chuàng)建默認(rèn)的 CacheManager纬向。同樣的择浊,將使用默認(rèn)的 javax.cache.configuration.MutableConfiguration 創(chuàng)建緩存。

為了控制使用哪個(gè)提供程序?yàn)?CacheManager 和 Caches 指定配置罢猪,您可以使用以下兩個(gè)屬性:

<property
    name="hibernate.javax.cache.provider"
    value="org.ehcache.jsr107.EhcacheCachingProvider"/>
<property
    name="hibernate.javax.cache.uri"
    value="file:/path/to/ehcache.xml"/>

使用 EhCache 2.0 構(gòu)建二級(jí)緩存

使用 Ehcache 的內(nèi)置集成要求我們引入 org.hibernate:hibernate-ehcahe jar 作為依賴近她。

同樣的我們需要配置 RegionFactory 為:hibernate.cache.region.factory_class: org.hibernate.cache.ehcache.EhCacheRegionFactory

強(qiáng)制 Hibernate 使用單個(gè) Ehcache CacheManager:hibernate.cache.region.factory_class: org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory 膳帕。

使用 EhCache 3.0 構(gòu)建二級(jí)緩存

使用 EhCache 3.0 時(shí)粘捎,hibernate 并沒(méi)有默認(rèn)的模塊支持。但是我們可以通過(guò) JCache 來(lái)指定具體的實(shí)現(xiàn)完成危彩。

我們同時(shí)引入 org.ehcache:ehcacheorg.hibernate:hibernate-jcache攒磨,這里我們并沒(méi)有指明具體版本,因?yàn)槲覀兪褂?spring boot starter 管理的版本汤徽,具體可以查看自己 spring boot starter 的版本和依賴版本娩缰。

我們可以指定服務(wù)提供者 hibernate.javax.cache.provider 來(lái)完成配置。但是 JCacheRegionFactory 會(huì)使用默認(rèn)的 MutableConfiguration 來(lái)創(chuàng)建 Cache谒府,如果我們需要使用 EhCache 的配置來(lái)定義緩存拼坎,我們需要自己定義 RegionFactory 并以此作為 RegionFactory 配置。

為了防止 JCacheRegionFactory 自動(dòng)創(chuàng)建緩存完疫,我們可以按如下配置:

public class NoDefaultJCacheRegionFactory extends JCacheRegionFactory {

    public static final String EXCEPTION_MESSAGE = "All Hibernate caches should be created upfront. " +
        "Please update CacheConfiguration.java to add";

    @Override
    protected Cache<Object, Object> createCache(String regionName, Properties properties, CacheDataDescription
        metadata) {
        throw new IllegalStateException(EXCEPTION_MESSAGE + " " + regionName);
    }
}

然后指定二級(jí)緩存 CacheManager 的創(chuàng)建方法泰鸡,并使用當(dāng)前的 CalssLoader,修復(fù)了 Spring Boot 2.0.3 中引入的 Spring 類(lèi)加載器問(wèn)題壳鹤。 這允許對(duì) ehcache 使用相同的類(lèi)加載器盛龄,包括 Spring Cache 抽象和 Hibernate 2nd 級(jí)緩存。

public class BeanClassLoaderAwareJCacheRegionFactory extends NoDefaultJCacheRegionFactory {

    private static volatile ClassLoader classLoader;

    @Override
    protected CacheManager getCacheManager(Properties properties) {
        Objects.requireNonNull(classLoader, "Please set Spring's classloader in the setBeanClassLoader " +
            "method before using this class in Hibernate");

        CachingProvider cachingProvider = getCachingProvider(properties);
        String cacheManagerUri = getProp(properties, CONFIG_URI);

        URI uri = getUri(cachingProvider, cacheManagerUri);
        CacheManager cacheManager = cachingProvider.getCacheManager(uri, classLoader);

        // To prevent some class loader memory leak this might cause
        setBeanClassLoader(null);

        return cacheManager;
    }

    private URI getUri(CachingProvider cachingProvider, String cacheManagerUri) {
        URI uri;
        if (cacheManagerUri != null) {
            try {
                uri = new URI(cacheManagerUri);
            } catch (URISyntaxException e) {
                throw new CacheException("Couldn't create URI from " + cacheManagerUri, e);
            }
        } else {
            uri = cachingProvider.getDefaultURI();
        }
        return uri;
    }

    /**
     * This method must be called from a Spring Bean to get the classloader.
     * For example: BeanClassLoaderAwareJCacheRegionFactory.setBeanClassLoader(this.getClass().getClassLoader());
     *
     * @param classLoader The Spring classloader
     */
    public static void setBeanClassLoader(ClassLoader classLoader) {
        BeanClassLoaderAwareJCacheRegionFactory.classLoader = classLoader;
    }
}

然后芳誓,我們就可以使用 EhCache 的配置來(lái)創(chuàng)建緩存余舶。

public class CacheConfiguration {

    private final javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration;

    public CacheConfiguration(ApplicationProperties applicationProperties) {
        BeanClassLoaderAwareJCacheRegionFactory.setBeanClassLoader(this.getClass().getClassLoader());
        ApplicationProperties.Cache.Ehcache ehcache =
            applicationProperties.getCache().getEhcache();

        CacheEventListenerConfigurationBuilder cacheEventListenerConfiguration = CacheEventListenerConfigurationBuilder
            .newEventListenerConfiguration(new CacheListener(applicationProperties), EventType.CREATED, EventType.UPDATED, EventType.EVICTED, EventType.EXPIRED, EventType.REMOVED)
            .unordered().asynchronous();

        jcacheConfiguration = Eh107Configuration.fromEhcacheCacheConfiguration(
            CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, Object.class,
                ResourcePoolsBuilder.heap(ehcache.getMaxEntries()))
                .add(cacheEventListenerConfiguration)
                .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(ehcache.getTimeToLiveSeconds())))
                .build());
    }

    @Bean
    public JCacheManagerCustomizer cacheManagerCustomizer() {
        return cm -> {
         cm.createCache(com.zeral.domain.User.class.getName(), jcacheConfiguration);
            // application-needle-ehcache-add-entry
        };
    }
}

上述代碼使用了堆內(nèi)存儲(chǔ),并指定了最大對(duì)象數(shù)锹淌,指定了緩存鍵和值的類(lèi)型匿值,以及過(guò)期策略,并配置了緩存監(jiān)聽(tīng)器葛圃。

并使用了 JCacheManagerCustomizer 函數(shù)接口完成了 CacheManager 創(chuàng)建緩存千扔,之所以這么寫(xiě)是因?yàn)槿绻覀儧](méi)有顯式配置 JCacheManager憎妙,Spring Boot 會(huì)為我們自動(dòng)使用當(dāng)前環(huán)境下的服務(wù)提供者創(chuàng)建該對(duì)象,我們可以通過(guò)函數(shù)接口作為對(duì)象創(chuàng)建完畢的回調(diào)方法使用曲楚。

@Bean
@ConditionalOnMissingBean
public CacheManager jCacheCacheManager() throws IOException {
   CacheManager jCacheCacheManager = createCacheManager();
   List<String> cacheNames = this.cacheProperties.getCacheNames();
   if (!CollectionUtils.isEmpty(cacheNames)) {
      for (String cacheName : cacheNames) {
         jCacheCacheManager.createCache(cacheName, getDefaultCacheConfiguration());
      }
   }
   customize(jCacheCacheManager);
   return jCacheCacheManager;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末厘唾,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子龙誊,更是在濱河造成了極大的恐慌抚垃,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趟大,死亡現(xiàn)場(chǎng)離奇詭異鹤树,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)逊朽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)罕伯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人叽讳,你說(shuō)我怎么就攤上這事追他。” “怎么了岛蚤?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵邑狸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我涤妒,道長(zhǎng)单雾,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任她紫,我火速辦了婚禮硅堆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘贿讹。我一直安慰自己硬萍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布围详。 她就那樣靜靜地躺著,像睡著了一般祖屏。 火紅的嫁衣襯著肌膚如雪助赞。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,821評(píng)論 1 290
  • 那天袁勺,我揣著相機(jī)與錄音雹食,去河邊找鬼。 笑死期丰,一個(gè)胖子當(dāng)著我的面吹牛群叶,可吹牛的內(nèi)容都是我干的吃挑。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼街立,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼舶衬!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起赎离,我...
    開(kāi)封第一講書(shū)人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤逛犹,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后梁剔,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體虽画,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年荣病,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了码撰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡个盆,死狀恐怖脖岛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情砾省,我是刑警寧澤鸡岗,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站编兄,受9級(jí)特大地震影響轩性,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜狠鸳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一揣苏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧件舵,春花似錦卸察、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至临梗,卻和暖如春涡扼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盟庞。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工吃沪, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人什猖。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓票彪,卻偏偏與公主長(zhǎng)得像红淡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子降铸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容