一、Cache緩存的作用
隨着時間的積累,應用的使用用戶不斷增加,數據規模也越來越大,往往數據庫查詢操作會成爲影響用戶使用體驗的瓶頸,此時使用緩存往往是解決這一問題非常好的手段之一。Spring 3開始提供了強大的基於註解的緩存支持,可以通過註解配置方式低侵入的給原有Spring應用增加緩存功能,提高數據訪問性能。在Spring Boot中對於緩存的支持,提供了一系列的自動化配置,使我們可以非常方便的使用緩存。
1.JSR107
Java Caching定義了5個核心接口,分別是CachingProvider, CacheManager, Cache, Entry 和 Expiry。
示意圖:
- CachingProvider定義了創建、配置、獲取、管理和控制多個CacheManager。一個應用可 以在運行期訪問多個CachingProvider。
- CacheManager定義了創建、配置、獲取、管理和控制多個唯一命名的Cache,這些Cache 存在於CacheManager的上下文中。一個CacheManager僅被一個CachingProvider所擁有。
- Cache是一個類似Map的數據結構並臨時存儲以Key爲索引的值。一個Cache僅被一個 CacheManager所擁有。
- Entry是一個存儲在Cache中的key-value對。
- Expiry 每一個存儲在Cache中的條目有一個定義的有效期。一旦超過這個時間,條目爲過期 的狀態。一旦過期,條目將不可訪問、更新和刪除。緩存有效期可以通過ExpiryPolicy設置。
2.Spring緩存抽象
Spring從3.1開始定義了org.springframework.cache.Cache 和org.springframework.cache.CacheManager接口來統一不同的緩存技術; 並支持使用JCache(JSR-107)註解簡化我們開發。
- Cache接口爲緩存的組件規範定義,包含緩存的各種操作集合。
- Cache接口下Spring提供了各種xxxCache的實現;如RedisCache,EhCacheCache , ConcurrentMapCache。
- 每次調用需要緩存功能的方法時,Spring會檢查檢查指定參數的指定的目標方法是否 已經被調用過;如果有就直接從緩存中獲取方法調用後的結果,如果沒有就調用方法 並緩存結果後返回給用戶。下次調用直接從緩存中獲取。
- 使用Spring緩存抽象時我們需要關注以下兩點:
1.、確定方法需要被緩存以及他們的緩存策略
2、從緩存中讀取之前緩存存儲的數據
二、幾個重要概念&緩存註解
Cache | 緩存接口,定義緩存操作。實現有:RedisCache、EhCacheCache、 ConcurrentMapCache等 |
CacheManager |
緩存管理器,管理各種緩存(Cache)組件 |
@Cacheable |
主要針對方法配置,能夠根據方法的請求參數對其結果進行緩存 |
@CacheEvict | 清空緩存 |
@CachePut |
保證方法被調用,又希望結果被緩存。 |
@EnableCaching |
開啓基於註解的緩存 |
keyGenerator |
緩存數據時key生成策略 |
serialize | 緩存數據時value序列化策略 |
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=”#userNam e.length()>2”)
|
allEntries (@CacheEvict )
|
是否清空所有緩存內容,缺省爲 false,如果指定爲 true,則方法調用後將立即清空所有緩存
|
例如:@CachEvict(value=”testcache”,allEntries=true)
|
beforeInvocation (@CacheEvict)
|
是否在方法執行前就清空,缺省爲 false,如果指定 爲 true,則在方法還沒有執行的時候就清空緩存, 缺省情況下,如果方法執行拋出異常,則不會清空 緩存
|
例如:@CachEvict(value=”testcache”, beforeInvocation=true) |
unless (@CachePut) (@Cacheable) | 用於否決緩存的,不像condition,該表達式只在方 法執行之後判斷,此時可以拿到返回值result進行判 斷。條件爲true不會緩存,fasle才緩存 | 例如:@Cacheable(value=”testcache”,unless=”#result == null”) |
名字 | 位置 | 描述 | 實例 |
methodName | root object | 當前被調用的方法名 | #root.methodName |
method | root object | 當前被調用的方法 | #root.method.name |
target | root object | 當前被調用的目標對象 | #root.target |
targetClass | root object | 當前被調用的目標對象類 | #root.targetClass |
args | root object | 當前被調用的方法的參數列表 | #root.args[0] |
caches | root object |
當前方法調用使用的緩存列表(如@Cacheable(value={"cache1", "cache2"})),則有兩個cache。
|
#root.caches[0].name
|
argument name
|
evaluation context |
方法參數的名字. 可以直接 #參數名 ,也可以使用 #p0或#a0 的 形式,0代表參數的索引。
|
#a0、#p0 |
result | evaluation context |
方法執行後的返回值(僅當方法執行之後的判斷有效,如 ‘unless’,’cache put’的表達式 ’cache evict’的表達式 beforeInvocation=false)
|
#result |
三、SpringBoot緩存工作原理以及@Cacheable運行流程
1.如果需要分析自動配置的原理就需要分析自動配置類:CacheAutoConfiguration:
2.這個自動配置中導入了一個類CacheConfigurationImportSelector,這個類會引入一些緩存配置類。
3.在配置文件中設置屬性debug=true,這樣就會打印所有的配置報告。
4.通過打印日誌可以看出SimpleCacheConfiguration配置類默認生效。這個配置類給容器中註冊了一個CacheManager。
5.緩存方法運行之前,先按照cacheNames查詢緩存組件,第一次獲取緩存如果沒有緩存創建一個。
6.Cache中查找緩存的內容會使用一個key,默認就是方法的參數。如果沒有參數使用SimpleKey生成。
四、SpringBoot中Cache緩存的使用
1.引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2.@EnableCaching開啓緩存
@MapperScan(basePackages = {"com.wang.cache.dao"})
@SpringBootApplication
@EnableCaching // 開啓緩存註解
public class SpringbootCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootCacheApplication.class, args);
}
}
3.@Cacheable緩存註解的使用 (標註在service業務層方法上)
執行流程:先執行@Cacheable註解中的getCache(String name)方法,根據name判斷ConcurrentMap中是否有此緩存,如果沒有緩存那麼創建緩存並保存數據,另外service層的方法也會執行。如果有緩存不再創建緩存,另外service層的方法也不會執行。
總結:先執行@Cacheable----->再執行service層的方法
@Cacheable註解的屬性如下:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
String condition() default "";
String unless() default "";
boolean sync() default false;
}
service層代碼
第一次查詢數據庫打印service類方法日誌,並把數據保存到Cahce中
第二次傳入相同參數不再執行service類方法,不會打印日誌,查詢的數據直接從緩存中獲取
@Service
public class PersonService {
@Autowired
PersonDao personDao;
/*1. @Cacheable的幾個屬性詳解:
* cacheNames/value:指定緩存組件的名字
* key:緩存數據使用的key,可以用它來指定。默認使用方法參數的值,一般不需要指定
* keyGenerator:作用和key一樣,二選一
* cacheManager和cacheResolver作用相同:指定緩存管理器,二選一
* condition:指定符合條件才緩存,比如:condition="#id>3"
* 也就是說傳入的參數id>3才緩存數據
* unless:否定緩存,當unless爲true時不緩存,可以獲取方法結果進行判斷
* sync:是否使用異步模式*/
//@Cacheable(cacheNames= "person")
//@Cacheable(cacheNames= "person",key="#id",condition="#id>3")
@Cacheable(cacheNames= "person",key="#id")
public Person queryPersonById(Integer id){
System.out.println("查詢"+id+"號員工信息");
Person person=new Person();
person.setId(id);
return personDao.query(person);
}
}
4.@CachePut必須結合@Cacheable一起使用,否則沒什麼意義
@CachePut的作用:即調用方法,又更新緩存數據 ,修改了數據庫中的數據,同時又更新了緩存!
@Service
public class PersonService {
@Autowired
PersonDao personDao;
/**
* @CachePut:即調用方法,又更新緩存數據
* 修改了數據庫中的數據,同時又更新了緩存
*
*運行時機:
* 1.先調用目標方法
* 2.將目標方法返回的結果緩存起來
*
* 測試步驟:
* 1.查詢1號的個人信息
* 2.以後查詢還是之前的結果
* 3.更新1號的個人信息
* 4.查詢一號員工返回的結果是什麼?
* 應該是更新後的員工
* 但只更新了數據庫,但沒有更新緩存是什麼原因?
* 5.如何解決緩存和數據庫同步更新?
* 這樣寫:@CachePut(cacheNames = "person",key = "#person.id")
* @CachePut(cacheNames = "person",key = "#result.id")
*/
@CachePut(cacheNames = "person",key = "#result.id")
public Person updatePerson(Person person){
System.out.println("修改"+person.getId()+"號員工信息");
personDao.update(person);
return person;
}
}
5.@CacheEvict也是結合@Cacheable一起使用纔有意義
@CacheEvict的作用:清除緩存中的指定數據或清除緩存中所有數據
@CacheEvict的屬性
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheEvict {
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
String condition() default "";
boolean allEntries() default false;
boolean beforeInvocation() default false;
}
service層代碼
@Service
public class PersonService {
@Autowired
PersonDao personDao;
/**
* @CacheEvict:清除緩存
* 1.key:指定要清除緩存中的某條數據
* 2.allEntries=true:刪除緩存中的所有數據
* beforeInvocation=false:默認是在方法之後執行清除緩存
* 3.beforeInvocation=true:現在是在方法執行之前執行清除緩存,
* 作用是:只清除緩存、不刪除數據庫數據
*/
//@CacheEvict(cacheNames = "person",key = "#id")
@CacheEvict(cacheNames = "person",allEntries=true)
public void deletePerson(Integer id){
System.out.println("刪除"+id+"號個人信息");
//刪除數據庫數據的同時刪除緩存數據
//personDao.delete(id);
/**
* beforeInvocation=true
* 使用在方法之前執行的好處:
* 1.如果方法出現異常,緩存依舊會被刪除
*/
//int a=1/0;
}
}
6.@Caching是@Cacheable、@CachePut、@CacheEvict註解的組合
@Caching的作用:此註解用於複雜的緩存操作
@Caching的屬性
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
Cacheable[] cacheable() default {};
CachePut[] put() default {};
CacheEvict[] evict() default {};
}
service層代碼
@Service
public class PersonService {
@Autowired
PersonDao personDao;
/**
* @Caching是 @Cacheable、@CachePut、@CacheEvict註解的組合
* 以下註解的含義:
* 1.當使用指定名字查詢數據庫後,數據保存到緩存
* 2.現在使用id、age就會直接查詢緩存,而不是查詢數據庫
*/
@Caching(
cacheable = {@Cacheable(value = "person",key="#name")},
put={ @CachePut(value = "person",key = "#result.id"),
@CachePut(value = "person",key = "#result.age")
}
)
public Person queryPersonByName(String name){
System.out.println("查詢的姓名:"+name);
return personDao.queryByName(name);
}
}
7.@CacheConfig主要用於配置該類中會用到的一些共用的緩存配置
@CacheConfig的作用:抽取@Cacheable、@CachePut、@CacheEvict的公共屬性值
@CacheConfig的屬性
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {
String[] cacheNames() default {};
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
}
servive層代碼
@Service
@CacheConfig(cacheNames = "person") //將cacheNames抽取出來
public class PersonService {
@Autowired
PersonDao personDao;
/*1. @Cacheable的幾個屬性詳解:
* cacheNames/value:指定緩存組件的名字
* key:緩存數據使用的key,可以用它來指定。默認使用方法參數的值,一般不需要指定
* keyGenerator:作用和key一樣,二選一
* cacheManager和cacheResolver作用相同:指定緩存管理器,二選一
* condition:指定符合條件才緩存,比如:condition="#id>3"
* 也就是說傳入的參數id>3才緩存數據
* unless:否定緩存,當unless爲true時不緩存,可以獲取方法結果進行判斷
* sync:是否使用異步模式*/
//@Cacheable(cacheNames= "person")
//@Cacheable(cacheNames= "person",key="#id",condition="#id>3")
@Cacheable(key="#id")
public Person queryPersonById(Integer id){
System.out.println("查詢"+id+"號員工信息");
Person person=new Person();
person.setId(id);
return personDao.query(person);
}
/**
* @CachePut:即調用方法,又更新緩存數據
* 修改了數據庫中的數據,同時又更新了緩存
*
*運行時機:
* 1.先調用目標方法
* 2.將目標方法返回的結果緩存起來
*
* 測試步驟:
* 1.查詢1號的個人信息
* 2.以後查詢還是之前的結果
* 3.更新1號的個人信息
* 4.查詢一號員工返回的結果是什麼?
* 應該是更新後的員工
* 但只更新了數據庫,但沒有更新緩存是什麼原因?
* 5.如何解決緩存和數據庫同步更新?
* 這樣寫:@CachePut(cacheNames = "person",key = "#person.id")
* @CachePut(cacheNames = "person",key = "#result.id")
*/
@CachePut(key = "#result.id")
public Person updatePerson(Person person){
System.out.println("修改"+person.getId()+"號員工信息");
personDao.update(person);
return person;
}
/**
* @CacheEvict:清除緩存
* 1.key:指定要清除緩存中的某條數據
* 2.allEntries=true:刪除緩存中的所有數據
* beforeInvocation=false:默認是在方法之後執行清除緩存
* 3.beforeInvocation=true:現在是在方法執行之前執行清除緩存,
* 作用是:只清除緩存、不刪除數據庫數據
*/
//@CacheEvict(cacheNames = "person",key = "#id")
@CacheEvict(cacheNames = "person",allEntries=true)
public void deletePerson(Integer id){
System.out.println("刪除"+id+"號個人信息");
//刪除數據庫數據的同時刪除緩存數據
//personDao.delete(id);
/**
* beforeInvocation=true
* 使用在方法之前執行的好處:
* 1.如果方法出現異常,緩存依舊會被刪除
*/
//int a=1/0;
}
/**
* @Caching是 @Cacheable、@CachePut、@CacheEvict註解的組合
* 以下註解的含義:
* 1.當使用指定名字查詢數據庫後,數據保存到緩存
* 2.現在使用id、age就會直接查詢緩存,而不是查詢數據庫
*/
@Caching(
cacheable = {@Cacheable(key="#name")},
put={ @CachePut(key = "#result.id"),
@CachePut(key = "#result.age")
}
)
public Person queryPersonByName(String name){
System.out.println("查詢的姓名:"+name);
return personDao.queryByName(name);
}
}