1. 程式人生 > 實用技巧 >Spring快取註解(@Cacheable @CachePut @CacheEvict @Caching)

Spring快取註解(@Cacheable @CachePut @CacheEvict @Caching)

Spring快取註解
  快取大家應該不會陌生吧,像大家熟知的redis、memcached都是現在所流行的用與快取資料的nosql,叫nosql的原因在於他們不是像我們jpa那樣將資料存到磁碟中,他們主要是將資料存到記憶體中,然後我們訪問資料的時候直接從記憶體加載出來,通過這樣的方法來提高我們程式的執行效率。至於redis等他們具體如何運轉的我就不一一介紹了.。
  如果我們做web開發的話,那麼應該都會接觸到spring的東西,正是因為有spring的存在,才使我們的開發效率更快,而我們本節主要講的就是基於spring幫我們實現的快取註解。
  我們回想一下,在我們日常的開發中,其實我們所做的後臺其實大多對資料的操作,而我們操作的資料基本上都是需要存到jpa資料庫中,我們前端顯示的時候就要通過查詢資料庫來將資料查詢出來並傳送給前端。如果併發小的話,其實我們是無需關注這個事情,但是如果你的併發量很大的時候,你頻繁的訪問資料庫,這個開銷是很大的,而在我們日常開發的專案中,其實是讀取資料的頻率是大於寫的頻率的,那麼我們為何不將讀取的資料放到快取中,然後下一次查詢的時候,會先去快取檢視是否有資料,如果沒有的話在去查詢資料庫。這樣的話,一些共有的資料是不是大家訪問的話就不會一一查詢資料庫了,其次是因為從記憶體中讀取資料是遠遠快於磁碟讀取的。當然,這種快取的工具,spring也已經幫我們整合好了。而接下來也會基於以下幾個註解來講是如何進行快取的。

@Cacheable
@CachePut
@CacheEvict
@Caching
@Cacheable
  這個註解的作用是負責將返回的資料進行快取,當我們第一次訪問的時候,會將查出來的資料先快取到redis中,當之後再發起訪問的時候,會先去檢視快取中是否存在該條資料,如果存在的話就直接從快取拿取該條資料。他裡面有這幾個引數


首先是value,這個作用是什麼,其實就是相當於我們取的名字,比如你的快取取名叫test,那麼你在redis快取裡面就會存一個tes名稱為test的快取,然後你查詢出來的資料就是放在這個test下面,下次通過這個value去快取中找,如果找到就直接拿取,沒找到就去資料庫查並將資料放入快取中,快取的key就是這個的value,快取中的value就是我們查詢的返回資料。
第二個引數key:這個是什麼呢?舉個列子,我們通過使用者id來獲取使用者的資料,比如a使用者存的是test的話,那麼b使用者也是通過這個方法去訪問資料庫的,但是快取中已經有資料了,這時候b使用者就不會去資料庫拿取資料,直接從快取讀,但是快取存的是a使用者的資料,那不是亂套了嗎,所以key就相當於子資料夾,比如我們的方法如下所示

Object zjtest(long id);


如果我們加了快取的話,那麼id就可以為key,這樣的話我們的a使用者訪問資料的時候,就會在快取生成一個test.a的key和value,而b使用者去訪問的時候就會去快取中查詢test.b的快取,但是沒有這條資料,所以b就會去資料庫拿取資料在快取,這樣就達到不同的使用者快取不同的資料。其實用個最好理解的方式就是test相當於一個資料夾,而a、b、c其實就相當於子資料夾。

第三個引數condition其實就是一種判斷的條件,就相當於我們的if,如果為true就快取,不為true就不快取。


@CachePut
這個註解的作用就其實有點類似於上面的,只是他的作用相當於我們資料庫中的update的作用,加上這個註解後,我們每次訪問資料都不會從快取中拿取資料,而是直接通過去資料庫查詢並將資料快取到redis中。可能有人會問,這樣有什麼作用呢?其實如果你是結合到cachable就有作用了,因為我們更新後資料是不是變化了,所以我們就需要將之前的資料的給清空掉,否則的話就會產生髒資料。
同樣他也有幾個引數需要填寫,其實就和cachable一樣的


這幾個引數的意義和上面我們講的一樣,所以就不在說了

@CacheEvict
我們上面的put的註解說了他是用於更新的,那麼如果我們增加了資料或者刪除了資料怎麼辦,比如通過使用者id來查詢使用者所購買的所有東西的資料,如果使用者又買了新東西或者刪除了某個物品的資訊,那麼我們怎麼辦,理論上我們是可以使用上面的註解,可是我們刪除和增加其實沒有必要去查詢新資料。所以我們這個CacheEvict註解的作用就出現了。他的作用其實就是幫我們清空指定的快取,他可以清空value,也可以清空value.key的資料。這樣的話我們可以針對性的去處理資料。

還是和之前一樣前面三個引數就不說了,我們直接從第四個說起allEntries的作用其實就是呼叫方法後立即清空快取,如果false的話就是不清空。

beforeinvocation 主要的作用是在方法執行後還是執行請清空,通常我們使用allentries,因為成功後我們才清空資料。

@Caching

最後一個註解的作用是啥呢?我們已經將了基本的,這個註解的作用就是聯合使用。看原始碼就知道

他接收的是一個數組,以及put、able、evict等引數。那麼這個註解什麼時候會用到呢?舉個例子,如果我們的方法操做了多個表資料的時候,我們就需要多個快取。比如要同時清空a和b的使用者的資料,那麼就需要使用caching來連線

@Caching(evict = {@CacheEvict(valie= "id", key = "#uuid"),
                      @CacheEvict(value= {"list", "other"}, allEntries = true)})

其實就是上面的這種配置方法。


如何將我們的專案整合進去呢:

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--  引入mybatis -->
      <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.1.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
             <version>8.0.11</version>
        </dependency>
  </dependencies>

然後啟用這個配置

@Configuration  
@EnableCaching//啟用快取,這個註解很重要;  
public class RedisCacheConfig extends CachingConfigurerSupport {  

    /** 
     * 快取管理器. 
     * @param redisTemplate 
     * @return 
     */  
    @Bean  
    public CacheManager cacheManager(RedisTemplate<?,?> redisTemplate) {  
        RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);  
       cacheManager.setDefaultExpiration(60*60);//設定快取的時間
       return cacheManager;  
    }  


    /** 
     * RedisTemplate快取操作類,類似於jdbcTemplate的一個類; 
     * 
     * 雖然CacheManager也能獲取到Cache物件,但是操作起來沒有那麼靈活; 
     * 
     * 這裡在擴充套件下:RedisTemplate這個類不見得很好操作,我們可以在進行擴充套件一個我們 
     * 
     * 自己的快取類,比如:RedisStorage類; 
     * 
     * @param factory : 通過Spring進行注入,引數在application.properties進行配置; 
     * @return 
     */  
    @Bean  
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {  
       RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();  
       redisTemplate.setConnectionFactory(factory);  

       //key序列化方式;(不然會出現亂碼;),但是如果方法上有Long等非String型別的話,會報型別轉換錯誤;  
       //所以在沒有自己定義key生成策略的時候,以下這個程式碼建議不要這麼寫,可以不配置或者自己實現ObjectRedisSerializer  
       //或者JdkSerializationRedisSerializer序列化方式;  
       RedisSerializer<String> redisSerializer = new StringRedisSerializer();//Long型別不可以會出現異常資訊;  
       redisTemplate.setKeySerializer(redisSerializer);  
       redisTemplate.setHashKeySerializer(redisSerializer);  
          redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
       return redisTemplate;  
    }