咱們知道絕大多數的網站/系統,最早遇到的一個性能瓶頸就是數據庫,使用緩存作數據庫的前置緩存,能夠很是有效地下降數據庫的壓力,從而提高整個系統的響應效率和併發量。前端
以往使用緩存時,一般建立好緩存工具類,使用時將對應的工具類注入,操做工具類在前端處理緩存的邏輯。其實這種方式是低效的,大部分使用緩存的場景是基於數據庫的緩存,這類緩存場景的邏輯每每是:若是緩存中存在數據,就從緩存中讀取,若是緩存中不存在數據或者數據失效,就再從數據庫中讀取。git
爲了實現這樣的邏輯,每每須要在業務代碼中寫不少的邏輯判斷,那麼有沒有通用的代碼來實現這樣的邏輯呢?其實有,按照這個邏輯咱們能夠寫一個工具類來實現,每次須要這樣判斷邏輯時調用工具類中的方法便可,還有沒有更優雅的使用方式呢?答案是確定的,若是咱們把這種固定的邏輯使用 Java 註解來實現,每次須要使用時只須要在對應的方法或者類上寫上註解便可。github
Spring 也看到了這樣的使用場景,因而有了註釋驅動的 Spring Cache。它的原理是 Spring Cache 利用了 Spring AOP 的動態代理技術,在項目啓動的時候動態生成它的代理類,在代理類中實現了對應的邏輯。spring
Spring Cache 是在 Spring 3.1 中引入的基於註釋(Annotation)的緩存(Cache)技術,它本質上不是一個具體的緩存實現方案,而是一個對緩存使用的抽象,經過在既有代碼中添加少許它定義的各類 Annotation,即可以達到緩存方法的返回對象的效果。數據庫
Spring 的緩存技術還具有至關的靈活性,不只可以使用 SpEL(Spring Expression Language)來定義緩存的 key 和各類 condition,還提供了開箱即用的緩存臨時存儲方案,也支持和主流的專業緩存如 EHCache 集成。瀏覽器
SpEL(Spring Expression Language)是一個支持運行時查詢和操做對象圖的強大的表達式語言,其語法相似於統一 EL,但提供了額外特性,顯式方法調用和基本字符串模板函數。緩存
其特色總結以下:併發
Spring Boot 提供了很是簡單的解決方案,這裏給你們演示最核心的三個註解:@Cacheable、@CacheEvict、@CachePut。spring-boot-starter-cache 是 Spring Boot 體系內提供使用 Spring Cache 的 Starter 包。app
在開始使用這三個註解以前,來介紹一個新的組件 spring-boot-starter-cache。函數
<dependency>
<groupId></groupId> org.springframework.boot
<artifactId></artifactId> spring-boot-starter-cache
</dependency>
spring-boot-starter-cache 是 Spring Boot 提供緩存支持的 starter 包,其會進行緩存的自動化配置和識別,Spring Boot 爲 Redis 自動配置了 RedisCacheConfiguration 等信息,spring-boot-starter-cache 中的註解也主要是使用了 Spring Cache 提供的支持。
@Cacheable 用來聲明方法是可緩存的,將結果存儲到緩存中以便後續使用相同參數調用時不需執行實際的方法,直接從緩存中取值。@Cacheable 能夠標記在一個方法上,也能夠標記在一個類上。當標記在一個方法上時表示該方法是支持緩存的,當標記在一個類上時則表示該類全部的方法都是支持緩存的。
咱們先來一個最簡單的例子體驗一下:
"/hello"@RequestMapping()
value"helloCache"@Cacheable(=)
public String hello(String name) {
out"沒有走緩存!" System..println();
return"hello " +name;
}
來測試一下,啓動項目後訪問網址 http://localhost:8080/hello?name=neo,輸出:沒有走緩存!,再次訪問網址 http://localhost:8080/hello?name=neo,輸出欄沒有變化,說明此次沒有走 hello() 這個方法,內容直接由緩存返回。
@Cacheable(value="helloCache") 這個註釋的意思是,當調用這個方法時,會從一個名叫 helloCache 的緩存中查詢,若是沒有,則執行實際的方法(也但是查詢數據庫),並將執行的結果存入緩存中,不然返回緩存中的對象。這裏的緩存中的 key 就是參數 name,value 就是返回的 String 值。
@Cacheable 支持以下幾個參數。
咱們把上面的方法稍微改爲這樣:
"/condition"@RequestMapping()
value"condition""#name.length() <= 4"@Cacheable(=,condition=)
public String condition(String name) {
out"沒有走緩存!" System..println();
return"hello " +name;
}
啓動後在瀏覽器中輸入網址 http://localhost:8080/condition?name=neo,第一次輸出欄輸出:沒有走緩存!再次執行無輸出,代表已經走緩存。在瀏覽器中輸入網址 http://localhost:8080/condition?name=ityouknow,瀏覽器執行屢次仍然一直輸出:沒有走緩存!說明條件 condition 生效。
結合數據庫的使用來作測試:
"/getUsers"@RequestMapping()
value"usersCache""#nickname""#nickname.length() >= 6"@Cacheable(=,key=,condition=)
public List<User> getUsers(String nickname) {
List<User> users=userRepository.findByNickname(nickname);
out"執行了數據庫操做" System..println();
return users;
}
啓動後在瀏覽器中輸入網址 http://localhost:8080/getUsers?nickname=neo。
輸出欄輸出:
selectasasasasasasfromuserwhereHibernate:user0_.idid1_0_, user0_.emailemail2_0_, user0_.nicknamenickname3_0_, user0_.pass_wordpass_wor4_0_, user0_.reg_timereg_time5_0_, user0_.user_nameuser_nam6_0_user0_user0_.nickname=?
執行了數據庫操做
屢次執行,仍然輸出上面的結果,說明每次請求都執行了數據庫操做,再輸入 http://localhost:8080/getUsers?nickname=ityoukonw 進行測試。只有第一次返回了上面的內容,再次執行輸出欄沒有變化,說明後面的請求都已經從緩存中拿取了數據。
最後總結一下:當執行到一個被 @Cacheable 註解的方法時,Spring 首先檢查 condition 條件是否知足,若是不知足,執行方法,返回;若是知足,在緩存空間中查找使用 key 存儲的對象,若是找到,將找到的結果返回,若是沒有找到執行方法,將方法的返回值以 key-value 對象的方式存入緩存中,而後方法返回。
須要注意的是當一個支持緩存的方法在對象內部被調用時是不會觸發緩存功能的。
項目運行中會對數據庫的信息進行更新,若是仍然使用 @Cacheable 就會致使數據庫的信息和緩存的信息不一致。在以往的項目中,咱們通常更新完數據庫後,再手動刪除掉 Redis 中對應的緩存,以保證數據的一致性。Spring 提供了另外的一種解決方案,可讓咱們以優雅的方式去更新緩存。
與 @Cacheable 不一樣的是使用 @CachePut 標註的方法在執行前,不會去檢查緩存中是否存在以前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的緩存中。
以上面的方法爲例,咱們再來作一個測試:
"/getPutUsers"@RequestMapping()
value"usersCache""#nickname"@CachePut(=,key=)
public List<User> getPutUsers(String nickname) {
List<User> users=userRepository.findByNickname(nickname);
out"執行了數據庫操做" System..println();
return users;
}
咱們新增一個 getPutUsers 方法,value、key 設置和 getUsers 方法保持一致,使用 @CachePut。同時手動在數據庫插入一條 nikename 爲 ityouknow 的用戶數據。
INSERTINTO`user`VALUES'1''ityouknow@126.com''ityouknow''123456''2018''keepSmile'(,,,,,);
在瀏覽器中輸入網址 http://localhost:8080/getUsers?nickname=ityouknow,並無返回用戶暱稱爲 ityouknow 的用戶信息,再次輸入網址 http://localhost:8080/getPutUsers?nickname=ityouknow 能夠查看到此用戶的信息,再次輸入網址 http://localhost:8080/getUsers?nickname=ityouknow 就能夠看到用戶暱稱爲 ityouknow 的信息了。
說明執行在方法上聲明 @CachePut 會自動執行方法,並將結果存入緩存。
@CachePut 配置方法
能夠看出 @CachePut 的參數和使用方法基本和 @Cacheable 一致。
@CachePut 也能夠標註在類上和方法上。
@CacheEvict 是用來標註在須要清除緩存元素的方法或類上的,當標記在一個類上時表示其中全部的方法的執行都會觸發緩存的清除操做。@CacheEvict 能夠指定的屬性有 value、key、condition、allEntries 和 beforeInvocation,其中 value、key 和 condition 的語義與 @Cacheable 對應的屬性相似。
即 value 表示清除操做是發生在哪些 Cache 上的(對應 Cache 的名稱);key 表示須要清除的是哪一個 key,如未指定則會使用默認策略生成的 key;condition 表示清除操做發生的條件。下面來介紹一下新出現的兩個屬性 allEntries 和 beforeInvocation。
allEntries 屬性
allEntries 是 boolean 類型,表示是否須要清除緩存中的全部元素,默認爲 false,表示不須要。當指定了 allEntries 爲 true 時,Spring Cache 將忽略指定的 key,有的時候咱們須要 Cache 一下清除全部的元素,這比一個一個清除元素更有效率。
在上一個方法中咱們使用註解:@CachePut(value="usersCache",key="#nickname")
來更新緩存,但若是不寫key="#nickname"
,Spring Boot 會以默認的 key 值去更新緩存,致使最上面的 getUsers() 方法並無獲取最新的數據。可是如今咱們使用 @CacheEvict 就能夠解決這個問題了,它會將全部以 usersCache 爲名的緩存所有清除。咱們來看個例子:
"/allEntries"@RequestMapping()
value"usersCache"true@CacheEvict(=, allEntries=)
public List<User> allEntries(String nickname) {
List<User> users=userRepository.findByNickname(nickname);
out"執行了數據庫操做" System..println();
return users;
}
手動修改用戶表的相關信息,好比註冊時間。在瀏覽器中輸入網址 http://localhost:8080/getUsers?nickname=ityouknow 發現緩存中的數據並無更新,再次訪問地址 http://localhost:8080/getUsers?nickname=ityouknow 會發現數據已經更新,而且輸出欄輸出「執行了數據庫操做」,這代表已經將名爲 usersCache 的緩存記錄清空了。
beforeInvocation 屬性
清除操做默認是在對應方法成功執行以後觸發的,即方法若是由於拋出異常而未能成功返回時也不會觸發清除操做。使用 beforeInvocation 能夠改變觸發清除操做的時間,當咱們指定該屬性值爲 true 時,Spring 會在調用該方法以前清除緩存中的指定元素。
@RequestMapping"/beforeInvocation"()
@CacheEvict"usersCache"truetrue(value=, allEntries=, beforeInvocation=)
public void beforeInvocation() {
thrownew"test beforeInvocation" RuntimeException();
}
咱們來作一個測試,在方法中添加一個異常,訪問網址 http://localhost:8080/beforeInvocation查看 usersCache 的緩存是否被更新。
按照上面的實驗步驟,手動修改用戶表的相關信息,訪問網址 http://localhost:8080/getUsers?nickname=ityouknow 發現緩存中的數據並無更新;再訪問網址 http://localhost:8080/beforeInvocation 會報錯誤,先不用管這裏,再次訪問地址 http://localhost:8080/getUsers?nickname=ityouknow 會發現數據已經更新,而且輸出欄輸出「執行了數據庫操做」。這代表雖然在測試的過程當中方法拋出了異常,但緩存中名爲 usersCache 的記錄都已被清空。
@Cacheable 做用和配置方法
主要針對方法配置,可以根據方法的請求參數對其結果進行緩存:
主要參數 |
解釋 |
舉例 |
value |
緩存的名稱,在 spring 配置文件中定義,必須指定至少一個 |
如 @Cacheable(value="mycache") 或者 @Cacheable(value={"cache1","cache2"} |
key |
緩存的 key,能夠爲空,若是指定要按照 SpEL 表達式編寫,若是不指定,則缺省按照方法的全部參數進行組合 |
如 @Cacheable(value="testcache",key="#userName") |
condition |
緩存的條件,能夠爲空,使用 SpEL 編寫,返回 true 或者 false,只有爲 true 才進行緩存 |
如 @Cacheable(value="testcache",condition="#userName.length()>2") |
@CachePut 做用和配置方法
@CachePut 的做用是主要針對方法配置,可以根據方法的請求參數對其結果進行緩存,和 @Cacheable 不一樣的是,它每次都會觸發真實方法的調用。
主要參數 |
解釋 |
舉例 |
value |
緩存的名稱,在 spring 配置文件中定義,必須指定至少一個 |
如 @Cacheable(value="mycache") 或者 @Cacheable(value={"cache1","cache2"} |
key |
緩存的 key,能夠爲空,若是指定要按照 SpEL 表達式編寫,若是不指定,則缺省按照方法的全部參數進行組合 |
如 @Cacheable(value="testcache",key="#userName") |
condition |
緩存的條件,能夠爲空,使用 SpEL 編寫,返回 true 或者 false,只有爲 true 才進行緩存 |
如 @Cacheable(value="testcache",condition="#userName.length()>2") |
@CacheEvict 做用和配置方法
主要針對方法配置,可以根據必定的條件對緩存進行清空。
主要參數 |
解釋 |
舉例 |
value |
緩存的名稱,在 spring 配置文件中定義,必須指定至少一個 |
如 @CachEvict(value="mycache") 或者 @CachEvict(value={"cache1","cache2"} |
key |
緩存的 key,能夠爲空,若是指定要按照 SpEL 表達式編寫,若是不指定,則缺省按照方法的全部參數進行組合 |
如 @CachEvict(value="testcache",key="#userName") |
condition |
緩存的條件,能夠爲空,使用 SpEL 編寫,返回 true 或者 false,只有爲 true 才清空緩存 |
如 @CachEvict(value="testcache",condition="#userName.length()>2") |
allEntries |
是否清空全部緩存內容,缺省爲 false,若是指定爲 true,則方法調用後將當即清空全部緩存 |
如 @CachEvict(value="testcache",allEntries=true) |
beforeInvocation |
是否在方法執行前就清空,缺省爲 false,若是指定爲 true,則在方法尚未執行的時候就清空緩存,缺省狀況下,若是方法執行拋出異常,則不會清空緩存 |
如 @CachEvict(value="testcache",beforeInvocation=true) |
@Cacheable、@CacheEvict、@CachePut 三個註解很是靈活,知足了咱們對數據緩存的絕大多數使用場景,而且使用起來很是的簡單而又強大,在實際工做中咱們能夠靈活搭配使用。
Spring 提供了基於註釋驅動的 Spring Cache,它是一個對緩存使用的抽象,將咱們經常使用的緩存策略都進行了高度抽象,讓咱們在項目中使用時只須要添加幾個註解,便可完成大多數緩存策略的實現。Spring Boot Starter Cache 是 Spring Boot 提供給咱們在 Spring Boot 中使用 Spring Cache 的 Starter 包,集成後方便在 Spring Boot 體系中使用緩存。