什么是緩存
術(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)感耙。
JCache 核心概念
Java 的緩存 API 定義了五個(gè)核心接口:CachingProvider
褂乍,CacheManager
,Cache
即硼,Entry
和 ExpiryPolicy
逃片。
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)圖:
使用 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ǔ)慢铺根。
用戶管理的緩存
用戶管理的緩存為我們提供了一種直接配置緩存的簡(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ù):Ehcache
和 Infinispan
起宽。
我們可以通過(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.USE 和 CacheRetrieveMode.USE
|
默認(rèn)窟社。from/into 緩存讀取/寫(xiě)入數(shù)據(jù) |
CacheMode.REFRESH | CacheStoreMode.REFRESH` 和 `CacheRetrieveMode.BYPASS |
不從緩存中讀取券勺,而是在從數(shù)據(jù)庫(kù)加載時(shí)寫(xiě)入緩存 |
CacheMode.PUT |
CacheStoreMode.USE 和 CacheRetrieveMode.BYPASS
|
不從緩存中讀取,而是在從數(shù)據(jù)庫(kù)讀取時(shí)寫(xiě)入緩存 |
CacheMode.GET |
CacheStoreMode.BYPASS 和 CacheRetrieveMode.USE
|
從緩存中讀取灿里,但不寫(xiě)入緩存 |
CacheMode.IGNORE |
CacheStoreMode.BYPASS 和 CacheRetrieveMode.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
赊抖,CachePut
和 CacheEvict
。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:ehcache
和 org.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;
}