一、前言
從Spring3.1開始,Spring引入了對Cache的支持。其使用方法和原理都類似於Spring對事務管理的支持。Spring Cache是作用在方法上的,其核心思想是這樣的:當我們在調用一個緩存方法時會把該方法參數和返回結果作爲一個鍵值對存放在緩存中,等到下次利用同樣的參數來調用該方法時將不再執行該方法,而是直接從緩存中獲取結果進行返回。所以在使用Spring Cache的時候我們要保證我們緩存的方法對於相同的方法參數要有相同的返回結果。
SpringBoot中,CacheManager管理多個緩存Cache組件,對緩存的真正CRUD操作在Cache組件中,每一個緩存組件都有自己的名字。
SpringBoot緩存:加載緩存自動配置類–>緩存配置類—>默認生效配置類SimpleCacheConfiguration–>給容器中註冊一個CacheManager:ConcurrentMapCacheManager可以獲取和創建ConcurrentMap類型的緩存組件:作用是將數據保存在ConcurrentMap中。
從上面我們大概可以瞭解到,SpringBoot對緩存的管理,以及緩存的形式,最主要的就是:·我們的緩存數據以key:value形式交給緩存管理器CacheManager,然後保存在一個叫做ConcurrentMap的Map中。
下面我們就從實例中來看看幾個緩存註解的使用:
二、@Cacheable用法
@Cacheable註解可以作用於方法上或類上,作用於方法上表示該方法的返回值會被緩存起來,該方法具有緩存功能,而作用於類上表示該類中的所有方法都具有緩存功能。
下面我們以作用於方法上來進行實例分析:
@Cacheable(cacheNames = {"emp","temp"} ,key="#id",condition = "#id>0",unless = "#result==null")
public Employee findEmployeeById(Integer id){
System.out.println("查詢編號爲 "+id+" 的員工!");
return employeeMapper.findEmployeeById(id);
}
@Cacheable幾個常用屬性:
- cacheNames/value
緩存的名字,我們將我們的緩存數據交給緩存管理器CacheManager管理,因此我們需要指定我們緩存的名字,也就是放在哪個緩存中,這樣Springboot在存取緩存中的數據時候才知道要在哪個緩存中去找。 - key
這是一個非常重要的屬性,表示我們緩存數據的鍵,默認使用參數名作爲緩存的鍵,值就是我們方法的返回結果。
當然,key的寫法還支持SPEL表達式,這裏的表達式可以使用方法參數及它們對應的屬性。使用方法參數時我們可以直接使用“#參數名”
、“#p參數index”
、"#a參數index
"。“參數index” 表示參數列表中第幾個參數。下面是幾個使用參數作爲key的示例
//使用#參數名方式指定
@Cacheable(value="users", key="#id")
public User find(Integer id) {
return null;
}
//使用#p參數index方式,0就表示參數列表中的第一次參數
@Cacheable(value="users", key="#p0")
public User find(Integer id) {
return null;
}
//參數是對象的時候,我們可以使用#對象.屬性方法指定
@Cacheable(value="users", key="#user.id")
public User find(User user) {
return null;
}
//即使參數是對象,我們也能用a下標或者p下標方式獲取到對應參數中對應的對象
@Cacheable(value="users", key="#a0.id")
public User find(User user) {
return null;
}
當然,我們還可以通過編寫配置類方式自定義配置key的生成策略。注意包不要導錯喲。
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
import java.util.Arrays;
@Configuration
public class MyCacheConfig {
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator(){
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+ Arrays.asList(params).toString()+"]";
}
};
}
}
在@Cacheable中使用keyGenerator
屬性來進行使用我們的配置key生成策略。
@Cacheable(value="emp", keyGenerator = "myKeyGenerator")
key屬性和keyGenerator屬性我們只能二選一。
最後,我們的Spring還提供了一個root對象可以用來生成key。通過該root對象我們可以獲取到以下信息。我們來看看文檔中這部分內容:
屬性名稱 | 描述 | 示例 |
---|---|---|
methodName | 當前方法名 | #root.methodName |
method | 當前方法 | #root.method.name |
target | 當前被調用的對象 | #root.target |
targetClass | 當前被調用的對象的class | #root.targetClass |
args | 當前方法參數組成的數組 | #root.args[0] |
caches | 當前被調用的方法使用的Cache | #root.caches[0].name |
@Cacheable(value="emp", key = "#root.args[0]")
public User getUserById(Integer id){
//根據id查詢用戶信息
User user=userMapper.getUserById(id);
return user;
}
注:當我們要使用root對象的屬性作爲key時我們也可以將“#root”省略,因爲Spring默認使用的就是root對象的屬性。
- condition
有些時候我們並不希望緩存一個方法所有的返回結果。我只需要滿足條件的才進行緩存。通過condition屬性可以實現這一功能。condition屬性默認爲空,表示將緩存所有的調用情形。其值是通過SpringEL表達式來指定的,當爲true時表示進行緩存處理;當爲false時表示不進行緩存處理,即每次調用該方法時該方法都會執行一次。
@Cacheable(cacheNames = {"emp","temp"} ,key="#id",condition = "#id>0")
public Employee findEmployeeById(Integer id){
System.out.println("查詢編號爲 "+id+" 的員工!");
return employeeMapper.findEmployeeById(id);
}
在上面代碼中,我們使用condition屬性指定id大於0的結果才進行緩存。
- unless
否定緩存,當unless指定的條件爲true,方法的返回值就不會被緩存;可以獲取到結果進行判斷。 - sync
是否指定異步模式。
總結:@Cacheable標註的方法執行之前,先檢查緩存中有沒有這個數據,默認按照參數的值作爲key去查詢緩存,如果沒有就運行方法並將結果放入緩存。
三、@CachePut用法
在支持Spring Cache的環境下,對於使用@Cacheable標註的方法,Spring在每次執行前都會檢查Cache中是否存在相同key的緩存元素,如果存在就不再執行該方法,而是直接從緩存中獲取結果進行返回,否則纔會執行並將返回結果存入指定的緩存中。@CachePut也可以聲明一個方法支持緩存功能。與@Cacheable不同的是,使用@CachePut標註的方法在執行前不會去檢查緩存中是否存在之前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的緩存中
。
@CachePut(value="emp",key = "#result.id")
public Employee updateEmployee(Employee employee){
System.out.println("updateEmp:"+employee);
employeeMapper.updateEmployee(employee);
return employee;
}
使用result表示方法的返回結果,這是爲了同步查詢功能。
也可以使用參數列表:key="#employee.id"
進行同步更新緩存功能。
解釋如下:
1、查詢編號爲1的員工信息,放入緩存emp中。
2、更新編號爲1的員工信息,此時的key如果和查詢時使用的key不一樣,那麼,我們更新該員工之後緩存的員工信息的key值和查詢緩存中的key不一樣,那麼我們相當於是新添加了一組緩存數據。當再次執行查詢方法時候將不能得到更新之後的員工信息。
強調:@Cacheable的key是不能夠使用#result.id,其實是因爲第一次方法沒運行之前需要先去檢查緩存得到這個key,而此時緩存中並沒有這個key。
@Cacheput註解一般使用在保存,更新方法中。
四、@CacheEvict註解
@CacheEvict是用來標註在需要清除緩存元素的方法或類上的。當標記在一個類上時表示其中所有的方法的執行都會觸發緩存的清除操作。@CacheEvict可以指定的屬性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的語義與@Cacheable對應的屬性類似。即value表示清除操作是發生在哪些Cache上的(對應Cache的名稱);key表示需要清除的是哪個key,如未指定則會使用默認策略生成的key;condition表示清除操作發生的條件。下面看一下示例:
/**
* 刪除員工信息的時候同時刪除相應的緩存
* key指定要清除的數據,allEntries表示是否刪除所有緩存數據
* beforeInvocation=true:當我們指定該屬性值爲true時,會在調用該方法之前清除緩存中的指定元素。false表示在方法執行之後執行。默認爲false
* @param id
*/
@CacheEvict(value = "emp",key = "#id"/*,allEntries = true,beforeInvocation = false*/)
public void deleteEmployee(Integer id){
System.out.println("刪除編號爲"+id+"的員工");
employeeMapper.deleteEmployee(id);
}
五、@Caching定義複雜註解
@Caching註解可以讓我們在一個方法或者類上同時指定多個Spring Cache相關的註解。其擁有三個屬性:cacheable、put和evict,分別用於指定@Cacheable、@CachePut和@CacheEvict。
示例:
@Caching(
cacheable = {
@Cacheable(value = "emp",key = "#lastName")
},
put = {
@CachePut(value = "emp",key = "#result.id"),
@CachePut(value = "emp",key = "#result.email")
},
evict = {
@CacheEvict(cacheNames = "emp",allEntries = true)
}
)
public Employee findByName(String lastName){
return employeeMapper.findByLastName(lastName);
}
注:@Cacheput註解一定會執行方法,也就是說第一次按照lastName查詢之後,雖然緩存中存有該數據,但是查詢數據庫的方法一定會執行。也就是說,當我們通過名字查詢數據庫的時候,即使緩存中有該數據,但還是會進行數據庫的查詢,只因有@Cacheput存在。
六、 @CacheConfig註解
@CacheConfig用在類上,將公共緩存內容寫在該註解中,方法中這些屬性即可不用再寫了。
@CacheConfig(value="emp")
public class EmployeeService{
@Cacheable(key="#user.id")
public User find(User user) {
return null;
}
@Cacheable(key = "#root.args[0]")
public User getUserById(Integer id){
//根據id查詢用戶信息
User user=userMapper.getUserById(id);
return user;
}
}
Cache執行流程:
- 1、方法運行之前,先去查詢Cache(緩存組件),按照cacheNames指定名字獲取;
- (Cachemanager先獲取相應的緩存),第一次獲取緩存如果沒有緩存組件Cache會自動創建。
- 2、去Cache中查找緩存的內容,使用一個key,默認就是方法的參數 key是按照某種策略生成的:默認是使用keyGenerator生成的,默認使用SimpleKeyGenerator生成key,如果沒有參數:key=new SimpleKey(); 如果有一個參數:key=參數的值,如果有多個參數:key=new SimpleKey(params)。
- 3、沒有查找到緩存就調用目標方法。
- 4、將目標方法返回的結果放進緩存中。