SpringBoot使用Caffeine實(shí)現(xiàn)緩存的示例代碼
在本博客中,我們將探討如何使用Spring的緩存框架向任何Spring Boot應(yīng)用程序添加基本緩存支持,如果沒(méi)有正確實(shí)現(xiàn),還將探討緩存的一些問(wèn)題。最后但并非最不重要的一點(diǎn)是,我們將看幾個(gè)在真實(shí)場(chǎng)景中有用的緩存示例。
為什么要在應(yīng)用程序中添加緩存
在深入探討如何向應(yīng)用程序添加緩存之前,首先想到的問(wèn)題是為什么我們需要在應(yīng)用程序中使用緩存。
假設(shè)有一個(gè)包含客戶(hù)數(shù)據(jù)的應(yīng)用程序,用戶(hù)發(fā)出兩個(gè)請(qǐng)求來(lái)獲取客戶(hù)的數(shù)據(jù)(id=100)。
這就是沒(méi)有緩存時(shí)的情況。
如您所見(jiàn),對(duì)于每個(gè)請(qǐng)求,應(yīng)用程序都會(huì)轉(zhuǎn)到數(shù)據(jù)庫(kù)獲取數(shù)據(jù)。從數(shù)據(jù)庫(kù)獲取數(shù)據(jù)是一項(xiàng)成本高昂的操作,因?yàn)樗婕癐O。
但是,如果中間有一個(gè)緩存存儲(chǔ),可以在其中臨時(shí)存儲(chǔ)短時(shí)間的數(shù)據(jù),則可以將這些往返保存到數(shù)據(jù)庫(kù)并在IO時(shí)間保存。
這就是使用緩存時(shí)上述交互的樣子。
在Spring Boot應(yīng)用程序中實(shí)現(xiàn)緩存
SpringBoot提供了什么緩存支持?
- SpringBoot只提供了一個(gè)緩存抽象,您可以使用它將緩存透明、輕松地添加到Spring應(yīng)用程序中。
- 它不提供實(shí)際的緩存存儲(chǔ)。
- 但是,它可以與不同類(lèi)型的緩存提供程序一起工作,如Ehcache、Hazelcast、Redis、Caffee等。
- SpringBoot的緩存抽象可以添加到方法中(使用注釋?zhuān)?/li>
- 基本上,在執(zhí)行方法之前,Spring框架將檢查方法數(shù)據(jù)是否已經(jīng)緩存
- 如果是,則它將從緩存中獲取數(shù)據(jù)。
- 否則它將執(zhí)行該方法并緩存數(shù)據(jù)
- 它還提供了從緩存中更新或刪除數(shù)據(jù)的抽象。
- 在我們當(dāng)前的博客中,我們將了解如何使用Caffeine添加緩存,Caffeine是一種基于Java8的高性能、接近最優(yōu)的緩存庫(kù)。
您可以在 application.yaml
文件中指定使用哪個(gè)緩存提供程序來(lái)設(shè)置 spring.cache.type
屬性。
但是,如果沒(méi)有提供屬性,Spring將根據(jù)添加的庫(kù)自動(dòng)檢測(cè)緩存提供程序。
添加生成依賴(lài)項(xiàng)
現(xiàn)在假設(shè)您已經(jīng)啟動(dòng)并運(yùn)行了基本的Spring boot應(yīng)用程序,讓我們添加緩存依賴(lài)項(xiàng)。
打開(kāi) build.gradle
文件,并添加以下依賴(lài)項(xiàng)以啟用Spring Boot的緩存
compile('org.springframework.boot:spring-boot-starter-cache')
接下來(lái)我們將添加對(duì)Caffeine的依賴(lài)
compile group: 'com.github.ben-manes.caffeine', name: 'caffeine', version: '2.8.5'
緩存配置
現(xiàn)在我們需要在Spring Boot應(yīng)用程序中啟用緩存。
為此,我們需要?jiǎng)?chuàng)建一個(gè)配置類(lèi)并提供注釋 @EnableCaching
。
@Configuration @EnableCaching public class CacheConfig { }
現(xiàn)在這個(gè)類(lèi)是一個(gè)空類(lèi),但是我們可以向它添加更多配置(如果需要)。
現(xiàn)在我們已經(jīng)啟用了緩存,讓我們提供緩存名稱(chēng)和緩存屬性的配置,如緩存大小、緩存過(guò)期時(shí)間等
最簡(jiǎn)單的方法是在 application.yaml
中添加配置
spring: cache: cache-names: customers, users, roles caffeine: spec: maximumSize=500, expireAfterAccess=60s
上述配置執(zhí)行以下操作
- 將可用緩存名稱(chēng)限制為客戶(hù)、用戶(hù)和角色。將最大緩存大小設(shè)置為500。
- 當(dāng)緩存中的對(duì)象數(shù)達(dá)到此限制時(shí),將根據(jù)緩存逐出策略從緩存中刪除對(duì)象。將緩存過(guò)期時(shí)間設(shè)置為1分鐘。
- 這意味著項(xiàng)目將在添加到緩存1分鐘后從緩存中刪除。
還有另一種配置緩存的方法,而不是在 application.yaml
文件中配置緩存。
您可以在緩存配置類(lèi)中添加并提供一個(gè) CacheManager
Bean,該Bean可以完成與上面在 application.yaml
中的配置完全相同的工作
@Bean public CacheManager cacheManager() { Caffeine<Object, Object> caffeineCacheBuilder = Caffeine.newBuilder() .maximumSize(500) .expireAfterAccess( 1, TimeUnit.MINUTES); CaffeineCacheManager cacheManager = new CaffeineCacheManager( "customers", "roles", "users"); cacheManager.setCaffeine(caffeineCacheBuilder); return cacheManager; }
在我們的代碼示例中,我們將使用Java配置。
我們可以在Java中做更多的事情,比如配置 RemovalListener
,當(dāng)一個(gè)項(xiàng)從緩存中刪除時(shí)執(zhí)行 RemovalListener
,或者啟用緩存統(tǒng)計(jì)記錄,等等。
緩存方法結(jié)果
在我們使用的示例Spring boot應(yīng)用程序中,我們已經(jīng)有了以下API GET /API/v1/customer/{id}
來(lái)檢索客戶(hù)記錄。
我們將向CustomerService類(lèi)的 getCustomerByd(longCustomerId)
方法添加緩存。
要做到這一點(diǎn),我們只需要做兩件事
1. 將注釋 @CacheConfig(cacheNames=“customers”)
添加到 CustomerService
類(lèi)
提供此選項(xiàng)將確保 CustomerService
的所有可緩存方法都將使用緩存名稱(chēng)“customers”
2. 向方法 Optional getCustomerById(Long customerId)
添加注釋 @Cacheable
@Service @Log4j2 @CacheConfig(cacheNames = "customers") public class CustomerService { @Autowired private CustomerRepository customerRepository; @Cacheable public Optional<Customer> getCustomerById(Long customerId) { log.info("Fetching customer by id: {}", customerId); return customerRepository.findById(customerId); } }
另外,在方法 getCustomerById()
中添加一個(gè) LOGGER
語(yǔ)句,以便我們知道服務(wù)方法是否得到執(zhí)行,或者值是否從緩存返回。
復(fù)制代碼 代碼如下:log.info("Fetching customer by id: {}", customerId);
測(cè)試緩存是否正常工作
這就是緩存工作所需的全部?jī)?nèi)容。現(xiàn)在是測(cè)試緩存的時(shí)候了。
啟動(dòng)您的應(yīng)用程序,并點(diǎn)擊客戶(hù)獲取url
http://localhost:8080/api/v1/customer/
在第一次API調(diào)用之后,您將在日志中看到以下行—“ Fetching customer by id
”。
但是,如果再次點(diǎn)擊API,您將不會(huì)在日志中看到任何內(nèi)容。這意味著該方法沒(méi)有得到執(zhí)行,并且從緩存返回客戶(hù)記錄。
現(xiàn)在等待一分鐘(因?yàn)榫彺孢^(guò)期時(shí)間設(shè)置為1分鐘)。
一分鐘后再次點(diǎn)擊GETAPI,您將看到下面的語(yǔ)句再次被記錄——“通過(guò)id獲取客戶(hù)”。
這意味著客戶(hù)記錄在1分鐘后從緩存中刪除,必須再次從數(shù)據(jù)庫(kù)中獲取。
為什么緩存有時(shí)會(huì)很危險(xiǎn)
緩存更新/失效
通常我們緩存 GET
調(diào)用,以提高性能。
但我們需要非常小心的是緩存對(duì)象的更新/刪除。
@CachePut @cacheexecute
如果未將 @CachePut/@cacheexecute
放入更新/刪除方法中,GET調(diào)用中緩存返回的對(duì)象將與數(shù)據(jù)庫(kù)中存儲(chǔ)的對(duì)象不同。考慮下面的示例場(chǎng)景。
如您所見(jiàn),第二個(gè)請(qǐng)求已將人名更新為“ John Smith
”。但由于它沒(méi)有更新緩存,因此從此處開(kāi)始的所有請(qǐng)求都將從緩存中獲取過(guò)時(shí)的個(gè)人記錄(“ John Doe
”),直到該項(xiàng)在緩存中被刪除/更新。
緩存復(fù)制
大多數(shù)現(xiàn)代web應(yīng)用程序通常有多個(gè)應(yīng)用程序節(jié)點(diǎn),并且在大多數(shù)情況下都有一個(gè)負(fù)載平衡器,可以將用戶(hù)請(qǐng)求重定向到一個(gè)可用的應(yīng)用程序節(jié)點(diǎn)。
這種類(lèi)型的部署為應(yīng)用程序提供了可伸縮性,任何用戶(hù)請(qǐng)求都可以由任何一個(gè)可用的應(yīng)用程序節(jié)點(diǎn)提供服務(wù)。
在這些分布式環(huán)境(具有多個(gè)應(yīng)用服務(wù)器節(jié)點(diǎn))中,緩存可以通過(guò)兩種方式實(shí)現(xiàn)
- 應(yīng)用服務(wù)器中的嵌入式緩存(正如我們現(xiàn)在看到的)
- 遠(yuǎn)程緩存服務(wù)器
嵌入式緩存
嵌入式緩存駐留在應(yīng)用程序服務(wù)器中,它隨應(yīng)用程序服務(wù)器啟動(dòng)/停止。由于每臺(tái)服務(wù)器都有自己的緩存副本,因此對(duì)其緩存的任何更改/更新都不會(huì)自動(dòng)反映在其他應(yīng)用程序服務(wù)器的緩存中。
考慮具有嵌入式緩存的多節(jié)點(diǎn)應(yīng)用服務(wù)器的下面場(chǎng)景,其中用戶(hù)可以根據(jù)應(yīng)用服務(wù)器為其請(qǐng)求服務(wù)而得到不同的結(jié)果。
正如您在上面的示例中所看到的,更新請(qǐng)求更新了 Application Node2
的數(shù)據(jù)庫(kù)和嵌入式緩存。
但是, Application Node1
的嵌入式緩存未更新,并且包含過(guò)時(shí)數(shù)據(jù)。因此, Application Node1
的任何請(qǐng)求都將繼續(xù)服務(wù)于舊數(shù)據(jù)。
要解決這個(gè)問(wèn)題,您需要實(shí)現(xiàn) CACHE REPLICATION
—其中任何一個(gè)緩存中的任何更新都會(huì)自動(dòng)復(fù)制到其他緩存(下圖中顯示為藍(lán)色虛線)
遠(yuǎn)程緩存服務(wù)器
解決上述問(wèn)題的另一種方法是使用遠(yuǎn)程緩存服務(wù)器(如下所示)。
然而,這種方法的最大缺點(diǎn)是增加了響應(yīng)時(shí)間——這是由于從遠(yuǎn)程緩存服務(wù)器獲取數(shù)據(jù)時(shí)的網(wǎng)絡(luò)延遲(與內(nèi)存緩存相比)
緩存自定義
到目前為止,我們看到的緩存示例是向應(yīng)用程序添加基本緩存所需的唯一代碼。
然而,現(xiàn)實(shí)世界的場(chǎng)景可能不是那么簡(jiǎn)單,可能需要進(jìn)行一些定制。在本節(jié)中,我們將看到幾個(gè)這樣的例子
緩存密鑰
我們知道緩存是密鑰、值對(duì)的存儲(chǔ)。
示例1:默認(rèn)緩存鍵–具有單參數(shù)的方法
最簡(jiǎn)單的緩存鍵是當(dāng)方法只有一個(gè)參數(shù),并且該參數(shù)成為緩存鍵時(shí)。在下面的示例中, Long customerId
是緩存鍵
示例2:默認(rèn)緩存鍵–具有多個(gè)參數(shù)的方法
在下面的示例中,緩存鍵是所有三個(gè)參數(shù)的SimpleKey– countryId
、 regionId
、 personId
。
示例3:自定義緩存密鑰
在下面的示例中,我們將此人的 emailAddress
指定為緩存的密鑰
示例4:使用 KeyGenerator
的自定義緩存密鑰
讓我們看看下面的示例–如果要緩存當(dāng)前登錄用戶(hù)的所有角色,該怎么辦。
該方法中沒(méi)有提供任何參數(shù),該方法在內(nèi)部獲取當(dāng)前登錄用戶(hù)并返回其角色。
為了實(shí)現(xiàn)這個(gè)需求,我們需要?jiǎng)?chuàng)建一個(gè)如下所示的自定義密鑰生成器
然后我們可以在我們的方法中使用這個(gè)鍵生成器,如下所示。
條件緩存
在某些用例中,我們只希望在滿(mǎn)足某些條件的情況下緩存結(jié)果
示例1(支持 java.util.Optional
–僅當(dāng)存在時(shí)才緩存)
僅當(dāng)結(jié)果中存在 person
對(duì)象時(shí),才緩存 person
對(duì)象。
@Cacheable( value = "persons", unless = "#result?.id") public Optional<Person> getPerson(Long personId)
示例2(如果需要,by-pass緩存)
@Cacheable(value = "persons", condition="#fetchFromCache") public Optional<Person> getPerson(long personId, boolean fetchFromCache)
僅當(dāng)方法參數(shù)“ fetchFromCache
”為true時(shí),才從緩存中獲取人員。通過(guò)這種方式,方法的調(diào)用方有時(shí)可以決定繞過(guò)緩存并直接從數(shù)據(jù)庫(kù)獲取值。
示例3(基于對(duì)象屬性的條件計(jì)算)
僅當(dāng)價(jià)格低于500且產(chǎn)品有庫(kù)存時(shí),才緩存產(chǎn)品。
@Cacheable( value="products", condition="#product.price<500", unless="#result.outOfStock") public Product findProduct(Product product)
@CachePut
我們已經(jīng)看到 @Cacheable
用于將項(xiàng)目放入緩存。
但是,如果該對(duì)象被更新,并且我們想要更新緩存,該怎么辦?
我們已經(jīng)在前面的一節(jié)中看到,不更新緩存post任何更新操作都可能導(dǎo)致從緩存返回錯(cuò)誤的結(jié)果。
@CachePut(key = "#person.id") public Person update(Person person)
但是如果 @Cacheable
和 @CachePut
都將一個(gè)項(xiàng)目放入緩存,它們之間有什么區(qū)別?
主要區(qū)別在于實(shí)際的方法執(zhí)行
@Cacheable @CachePut
緩存失效
緩存失效與將對(duì)象放入緩存一樣重要。
當(dāng)我們想要從緩存中刪除一個(gè)或多個(gè)對(duì)象時(shí),有很多場(chǎng)景。讓我們看一些例子。
例1
假設(shè)我們有一個(gè)用于批量導(dǎo)入個(gè)人記錄的API。
我們希望在調(diào)用此方法之前,應(yīng)該清除整個(gè) person
緩存(因?yàn)榇蠖鄶?shù) person
記錄可能會(huì)在導(dǎo)入時(shí)更新,而緩存可能會(huì)過(guò)時(shí))。我們可以這樣做如下
@CacheEvict( value = "persons", allEntries = true, beforeInvocation = true) public void importPersons()
例2
我們有一個(gè)Delete Person API,我們希望它在刪除時(shí)也能從緩存中刪除 Person
記錄。
@CacheEvict( value = "persons", key = "#person.emailAddress") public void deletePerson(Person person)
默認(rèn)情況下 @CacheEvict
在方法調(diào)用后運(yùn)行。
到此這篇關(guān)于SpringBoot使用Caffeine實(shí)現(xiàn)緩存的示例代碼的文章就介紹到這了,更多相關(guān)SpringBoot Caffeine緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IntelliJ?IDEA?2020.2?全家桶及以下版本激活工具大全【喜訊】
這篇文章主要介紹了IntelliJ?IDEA?2020.2?全家桶及以下版本激活工具大全【喜訊】,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09SpringBoot如何使用自定義注解實(shí)現(xiàn)接口限流
這篇文章主要介紹了SpringBoot如何使用自定義注解實(shí)現(xiàn)接口限流,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06Java中的三種標(biāo)準(zhǔn)注解和四種元注解說(shuō)明
這篇文章主要介紹了Java中的三種標(biāo)準(zhǔn)注解和四種元注解說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02springboot中RestTemplate發(fā)送HTTP請(qǐng)求的實(shí)現(xiàn)示例
RestTemplate是一個(gè) spring-web 提供的執(zhí)行HTTP請(qǐng)求的同步阻塞式工具類(lèi),本文就來(lái)介紹一下RestTemplate發(fā)送HTTP請(qǐng)求,具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03idea中方法、注釋、導(dǎo)入類(lèi)折疊或是展開(kāi)的設(shè)置方法
這篇文章主要介紹了idea中方法、注釋、導(dǎo)入類(lèi)折疊或是展開(kāi)的設(shè)置,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04詳解Spring Boot 打包分離依賴(lài)JAR 和配置文件
這篇文章主要介紹了Spring Boot 打包分離依賴(lài)JAR 和配置文件,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11