第四篇:SpringBoot中Cache緩存的使用

一、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序列化策略
@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=”#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”)
Cache SpEL available metadata
名字 位置 描述 實例
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);
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章