SpringBoot使用快取及原理
一.JSR107
Java Caching 定義了五個核心介面,分別是CachingProvider,CacheManager,Cache,Entry和Expiry。
-
CachingProvider 定義了建立、配置、獲取、管理和控制多個CacheManager。一個應用可以在執行期訪問多個CacheProvider。
-
CacheManager 定義了建立、配置、獲取、管理和控制多個唯一命名的Cache,這些Cache存在於CacheManager的上下文彙總。一個CacheManager僅被一個CachingProvider所擁有。
-
Cache 是一個類似Map的資料結構並臨時儲存以Key為索引的值。一個Cache僅被一個CacheManager所擁有。
-
Entry 是一個儲存在Cache中的key-value對.
-
Expiry 每一個儲存在Cache中的條目有一個定義的有效期。一旦超過這個時間,條目為過期的狀態。一旦過期,條目將不可訪問、更新和刪除。快取有效期可以通過ExpiryPolicy設定。
如下圖所示:
二 Spring快取抽象(下面會有具體Springboot程式碼演示)
Spring從3.1開始定義了org.springframework.cache.Cache和org.springframework.cache.CacheManager介面來統一不同的快取技術;並支援使用JCache(JSR-107)註解簡化我們開發; 1 Cache
2 Cache介面下Spring提供了各種xxxCache的實現,如RedisCache,EhCacheCache , ConcurrentMapCache等;每次呼叫需要快取功能的方法時,Spring會檢查檢查指定引數的指定的目標方法是否已經被呼叫過;如果有就直接從快取中獲取方法呼叫後的結果,如果沒有就呼叫方法並快取結果返回給使用者。下次直接從快取中獲取。
3 使用Spring快取抽象時我們需要關注以下兩點;
第一點就是確定方法需要被快取以及他們的快取策略 ,第二點就是從快取中讀取之前快取儲存的資料,
瞭解jdbc的朋友就會很清楚,這就跟面向jdbc程式設計是一個道理,統一一個規範,統一面向jdbc程式設計。
如下圖所示:
三 快取註解
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=”#userName.length()>2”) |
allEntries (@CacheEvict) |
是否清空所有快取內容,預設為false,如果指定為true,則方法呼叫後將立即清空所有快取 |
例如:@CachEvict(value=”testcache”,allEntries=true) |
beforeInvocation (@CacheEvict) |
是否在方法執行前就清空,預設為false,如果指定為true,則在方法還沒有執行的時候就清空快取,預設清空下,如果方法執行丟擲異常,則不會清空快取 |
例如: @CachEvict(value=”testcache”,beforeInvocation=true) |
unless (@CachPut) (@Cacheable) |
用於否決快取,不行codition,該表示式只在方法執行之後判斷,此時可以拿到返回值result進行判斷。條件為true不會快取,false才快取 |
例如:@Cacheable(value=”testcache”,unless=”#result == null”) |
同樣支援spel表示式
四 快取使用(下面會有具體Springboot程式碼演示)
要在Springboot中使用快取需要以下幾步:
第一步: 匯入spring-boot-starter-cache模組
第二步: @EnableCaching開啟快取
第三步: 使用快取註解
1.在pom檔案中引入座標地址
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2 在主程式中開啟快取註解
@SpringBootApplication
@EnableCaching
public class Springboot01CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01CacheApplication.class, args);
}
}
3 開始測試程式碼,首先做好準備
javaBean:
public class Employee implements Serializable {
private Integer id;
private String lastName;
private String email;
private Integer gender; //性別 1男 0女
private Integer dId;
public Employee() {
super();
}
public Employee(Integer id, String lastName, String email, Integer gender, Integer dId) {
super();
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
this.dId = dId;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Integer getdId() {
return dId;
}
public void setdId(Integer dId) {
this.dId = dId;
}
@Override
public String toString() {
return "Employee [id=" + id + ", lastName=" + lastName + ", email=" + email + ", gender=" + gender + ", dId="
+ dId + "]";
}
}
controller層如下:
@RestController
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@GetMapping("/emp/{id}")
public Employee getEmployee(@PathVariable("id") Integer id){
Employee employee = employeeService.getEmp(id);
return employee;
}
@GetMapping("/emp")
public Employee update(Employee employee){
Employee emp = employeeService.updateEmp(employee);
return emp;
}
@GetMapping("/delemp")
public String deleteEmp(Integer id){
employeeService.deleteEmp(id);
return "success";
}
@GetMapping("/emp/lastname/{lastName}")
public Employee getEmpByLastName(@PathVariable("lastName") String lastName){
return employeeService.getEmpByLastName(lastName);
}
}
service層如下:
package com.atguigu.cache.service;
import com.atguigu.cache.bean.Employee;
import com.atguigu.cache.mapper.EmployeeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
@CacheConfig(cacheNames="emp"/*,cacheManager = "employeeCacheManager"*/) //抽取快取的公共配置
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
@Cacheable(value = {"emp"}/*,keyGenerator = "myKeyGenerator",condition = "#a0>1",unless = "#a0==2"*/)
public Employee getEmp(Integer id){
System.out.println("查詢"+id+"號員工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
@CachePut(/*value = "emp",*/key = "#result.id")
public Employee updateEmp(Employee employee){
System.out.println("updateEmp:"+employee);
employeeMapper.updateEmp(employee);
return employee;
}
@CacheEvict(value="emp",beforeInvocation = true/*key = "#id",*/)
public void deleteEmp(Integer id){
System.out.println("deleteEmp:"+id);
//employeeMapper.deleteEmpById(id);
int i = 10/0;
}
// @Caching 定義複雜的快取規則
@Caching(
cacheable = {
@Cacheable(/*value="emp",*/key = "#lastName")
},
put = {
@CachePut(/*value="emp",*/key = "#result.id"),
@CachePut(/*value="emp",*/key = "#result.email")
}
)
public Employee getEmpByLastName(String lastName){
return employeeMapper.getEmpByLastName(lastName);
}
}
mapper介面如下:
@Mapper
public interface EmployeeMapper {
@Select("SELECT * FROM employee WHERE id = #{id}")
public Employee getEmpById(Integer id);
@Update("UPDATE employee SET lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} WHERE id=#{id}")
public void updateEmp(Employee employee);
@Delete("DELETE FROM employee WHERE id=#{id}")
public void deleteEmpById(Integer id);
@Insert("INSERT INTO employee(lastName,email,gender,d_id) VALUES(#{lastName},#{email},#{gender},#{dId})")
public void insertEmployee(Employee employee);
@Select("SELECT * FROM employee WHERE lastName = #{lastName}")
Employee getEmpByLastName(String lastName);
}
application.properties如下:
spring.datasource.username=root
spring.datasource.password=admin
spring.datasource.url=jdbc:mysql://localhost:3306/springCache
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#開啟駝峰命名法
mybatis.configuration.map-underscore-to-camel-case=true
#列印sql語句日誌
logging.level.com.lsz.cache.mapper=debug
#控制檯列印配置資訊
debug=true
4 測試
執行專案,開啟瀏覽器輸入 http://localhost:8080/emp/1 ,可以看到瀏覽器返回的值
控制檯列印情況:
可以看到第一次獲取資料是從資料庫中獲取的,再次輸入 http://localhost:8080/emp/1
可以看到並沒有查詢資料庫,而是從快取中獲取的資料,這說明快取起作用了.
標記快取的方法
@Cacheable 標註的方法執行之前先來檢查快取中有沒有這個資料,預設按照引數的值作為key去查詢快取, 如果沒有就執行方法並將結果放入快取;以後再來呼叫就可以直接使用快取中的資料;
- 方法執行之前,先去查詢Cache(快取元件),按照cacheNames指定的名字獲取;(CacheManager先獲取相應的快取),第一次獲取快取時如果沒有Cache元件則會自動建立。
- 去cache中查詢快取的內容,使用key,預設是方法的引數;SimpleKeyGenerator生成key的預設策略:沒有引數:key=new SimpleKey() ,有一個引數,key= 引數的值,如果有多個引數,key=new SimpleKey(params);
- 沒有查到快取就呼叫目標方法
- 將目標方法的返回值放進快取中
幾個屬性:
- cacheNames/value:指定快取元件的名字;將方法的返回結果放在哪個快取中,是陣列的方式,可以指定多個快取;
- key:快取資料使用的key;可以用它來指定。預設是使用方法引數的值
- 編寫SpEL; #i d;引數id的值 #a0 #p0 #root.args[0]
- getEmp[2]
- keyGenerator:key的生成器;可以自己指定key的生成器的元件id
- key/keyGenerator:二選一使用;
- cacheManager:指定快取管理器;或者cacheResolver指定獲取解析器
- condition:指定符合條件的情況下才快取;
- condition = "#id>0"
- condition = "#a0>1":第一個引數的值>1的時候才進行快取
- unless:否定快取;當unless指定的條件為true,方法的返回值就不會被快取;可以獲取到結果進行判斷
- unless = "#result == null"
- unless = "#a0==2":如果第一個引數的值是2,結果不快取;
- sync:是否使用非同步模式
@CachePut 既呼叫目標方法,又更新快取
- 先呼叫目標方法
- 將目標方法的結果快取起來
@CacheEvict
/**
* @CacheEvict:快取清除
* key:指定要清除的資料
* allEntries = true:指定清除這個快取中所有的資料
* beforeInvocation = false:快取的清除是否在方法之前執行
* 預設代表快取清除操作是在方法執行之後執行;如果出現異常快取就不會清除
*
* beforeInvocation = true:
* 代表清除快取操作是在方法執行之前執行,無論方法是否出現異常,快取都清除
*
*
*/
@CacheEvict(value="emp",beforeInvocation = true,key = "#id")
public void deleteEmp(Integer id){
System.out.println("deleteEmp:"+id);
//employeeMapper.deleteEmpById(id);
int i = 10/0;
}
@Caching
// @Caching 定義複雜的快取規則
@Caching(
cacheable = {
@Cacheable(value="emp",key = "#lastName")
},
put = {
@CachePut(value="emp",key = "#result.id"),
@CachePut(value="emp",key = "#result.email")
}
)
public Employee getEmpByLastName(String lastName){
return employeeMapper.getEmpByLastName(lastName);
}