1. 程式人生 > >SpringBoot使用快取及原理

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序列化策略
@Cacheable/@CachePut/@CacheEvict主要引數
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=”testcachebeforeInvocation=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去查詢快取, 如果沒有就執行方法並將結果放入快取;以後再來呼叫就可以直接使用快取中的資料;

  1. 方法執行之前,先去查詢Cache(快取元件),按照cacheNames指定的名字獲取;(CacheManager先獲取相應的快取),第一次獲取快取時如果沒有Cache元件則會自動建立。
  2. 去cache中查詢快取的內容,使用key,預設是方法的引數;SimpleKeyGenerator生成key的預設策略:沒有引數:key=new SimpleKey() ,有一個引數,key= 引數的值,如果有多個引數,key=new SimpleKey(params);
  3. 沒有查到快取就呼叫目標方法
  4. 將目標方法的返回值放進快取中

幾個屬性:

  •           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 既呼叫目標方法,又更新快取 

  1. 先呼叫目標方法
  2. 將目標方法的結果快取起來

@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);
    }