1. 程式人生 > >SpringBoot——Cache使用原理及Redis整合

SpringBoot——Cache使用原理及Redis整合

前言及核心概念介紹

前言

本篇主要介紹SpringBoot2.x 中 Cahe 的原理及幾個主要註解,以及整合 Redis 作為快取的步驟

核心概念

先來看看核心介面的作用及關係圖:

CachingProvider  管理並建立CacheManager,一個CachingProvider可以管理多個CacheManager

CacheManager  管理並建立Cache,一個CacheManager管理多個Cache  

Cache  結構類似於Map<CacheName,Cache>,每個Cache有唯一的名字

Entry 結構類似於Map<KeyObject,ValueObject>,以鍵值對的形式儲存在Cache中

Expiry  Cache中每個條目都有有效期,過期則會被刪除或更新

 


 

 

一、SpringBoot中的快取結構:

要知道SpringBoot是通過XXXAutoConfiguration來向容器中註冊元件的

所以只要知道CacheAutoConfiguration註冊了哪些元件,我們就能入手進行分析

找到新增的元件

1、首先進入CacheAutoConfiguration
  可以看到其匯入了CacheConfigurationImportSelector
  從名字可以看出它是用來匯入快取配置類的

 

2、進入CacheConfigurationImportSelector
  這是一個靜態內部類,只有一個selectImports方法,方法的最後將字串陣列返回
  我們在方法上打上斷點進行測試



3、執行完第二步的方法,直接檢視最終的返回結果
  可以看到返回了很多XXXCacheConfiguration



4、在配置檔案中新增 debug=true
  要想知道到底用了哪個CacheConfiguration,我們可以在配置檔案中新增 debug=true 來檢視詳細的日誌
  啟動應用,在日誌中搜索CacheConfiguration,會發現只有SimpleCacheConfiguration是matched
  而其他的XXXCacheConfiguration都是Did not match
  結論:springboot預設使用SimpleCacheConfiguration作為快取配置類  
找到了配置類,順著配置類一層層進入,就能很快了解其中的結構


檢視快取結構
  1、進入預設配置類SimpleCacheConfiguration
  發現配置類中註冊了ConcurrentMapCacheManager作為CacheManager
  注意:@ConditionalOnMissingBean(CacheManager.class)註解
當容器中存在CacheManager時,本配置類就不會生效,而CacheManager是通過配置類建立的,也就是說,如果選擇了
其他的XXXCacheConfiguration,就會生成其他的CacheManager,本配置類就不會起作用。
這也是我們後面匯入Redis的startor後就會自動使用RedisCacheConfiguration的原因

   

2、進入ConcurrentMapCacheManager
  cacheMap正是ConcurrentMapCacheManager管理的Cache結構



3、通過除錯,找到這裡的Cache實現類為ConcurrentMapCache
  其中兩個屬性,name為cache的名字,store用於儲存鍵值對


到此為止,springboot的預設cache結構就出來了,接下來看看我們實現快取功能需要的常用註解以及他們要注意的地方

 

 

二、幾個關鍵註解

  1、@Cacheable

    標註在方法上,將方法返回的結果存入快取中

    可以指定cachename或value來對ConcurrentMapCache的name屬性進行設定

    也可以通過指定keyGenerator來制定快取鍵值對中生成key的規則 

    預設情況:name為傳入的引數,鍵值對中的值為方法返回結果 

   2、@CachePut

    標註在方法上,先執行方法,再用方法返回的值來更新快取內容

   3、@CacheEvict

    清除快取

   4、@Caching

    複雜的cache配置,可以在裡面配置上面的幾個註解

   5、@CacheConfig

    標註在類上,對類中的快取操作作統一配置

 

 

三、@Cacheable工作原理

  下來通過幾個重要的方法來展示@Cacheable工作原理

  這裡的測試方法將從資料庫中獲取1號員工的資料,方法上只標註了@Cacheable

 

第一次查詢

1、第一個重要方法:ConcurrentMapCacheManager.getCache(String name) 方法具體如下圖

 在ConcurrentMapCacheManager中,檢視cacheMap中是否存在名為emp的Cache

 如果存在則返回這個cache,如果不存在,就以傳入的name作為cache的name建立並返回

 這裡我們是不存在的,所以建立並返回一個名為emp的cache

 

2、由於是第一次查詢,快取中肯定是不存在任何員工的內容的,

所以接下來還是會執行真正的查詢方法,呼叫資料庫操作

 

3、返回結果之後,呼叫前面建立的cache,並呼叫其put方法,把員工id,員工資訊,以鍵值對的方式存入cache中

 

 

第二次查詢

有了上面的查詢,1號員工的資訊已經被快取起來了

接下來看看再次查詢1號員工會發生什麼

 

1、首先還是進入 ConcurrentMapCacheManager 的 getCache 方法查詢 cache

  因為第一次的操作,cacheMap中存在名為emp的cache,所以直接返回cache

 

2、接下來呼叫cache的lookup方法,通過鍵查詢值

 

 

 3、再接著方法將查詢到的值返回,然後就直接結束了,沒有呼叫實際的資料庫操作

 

總結

在第一次查詢時,會建立cache,然後呼叫方法,最後將方法的返回值存入cache

這樣在查詢相同內容時就直接從cache中獲取,無需呼叫方法操作資料庫來查詢

 

 

四、整合Redis

1、加入redis的startor,springboot會自動識別並使用RedisCacheConfiguration,具體原因上面有提到

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、開啟Redis服務(可以使用docker)

3、建立配置類,配置RedisCacheManager(配置序列號方式等屬性)

package com.tlj.cache.config;

import com.tlj.cache.bean.Department;
import com.tlj.cache.bean.Employee;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.redis.cache.CacheKeyPrefix;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import java.time.Duration;

@Configuration
public class RedisConfig {

//    @Bean
//    public RedisTemplate<Object, Employee> empRedisTemplate(
//            RedisConnectionFactory redisConnectionFactory){
//        RedisTemplate<Object,Employee> template=new RedisTemplate<Object,Employee>();
//        template.setConnectionFactory(redisConnectionFactory);
//        Jackson2JsonRedisSerializer<Employee> redisSerializer=new Jackson2JsonRedisSerializer<Employee>(Employee.class);
//        template.setDefaultSerializer(redisSerializer);
//        return template;
//    }

//    @Bean
//    public RedisCacheConfiguration redisCacheConfiguration(){
//        Jackson2JsonRedisSerializer<Employee> Jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
//        RedisSerializationContext.SerializationPair<Employee> pair = RedisSerializationContext.SerializationPair.fromSerializer(Jackson2JsonRedisSerializer);
//        return RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
//    }

    /**
     * 或者直接跳過RedisCacheConfiguration建立RedisCacheManager
     * (在多個manager的情況下可以在@CacheConfig指定)
     * @param redisConnectionFactory
     * @return
     */
    @Primary//多個Manager時需要設定
    @Bean
    public RedisCacheManager empCacheManager(RedisConnectionFactory redisConnectionFactory){
        //初始化一個RedisCacheWriter
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
        //設定CacheManager的值序列化方式為Jackson2JsonRedisSerializer
        Jackson2JsonRedisSerializer<Employee> Jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
        RedisSerializationContext.SerializationPair<Employee> pair = RedisSerializationContext.SerializationPair.fromSerializer(Jackson2JsonRedisSerializer);
        RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
        //設定預設超過期時間是30秒
        defaultCacheConfig.entryTtl(Duration.ofSeconds(30));
        //初始化RedisCacheManager
        RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
        return cacheManager;
    }

    @Bean
    public RedisCacheManager deptCacheManager(RedisConnectionFactory redisConnectionFactory){
        //初始化一個RedisCacheWriter
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
        //設定CacheManager的值序列化方式為Jackson2JsonRedisSerializer
        Jackson2JsonRedisSerializer<Department> Jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Department>(Department.class);
        RedisSerializationContext.SerializationPair<Department> pair = RedisSerializationContext.SerializationPair.fromSerializer(Jackson2JsonRedisSerializer);
        RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
        //設定預設超過期時間是30秒
        defaultCacheConfig.entryTtl(Duration.ofSeconds(30));
        //初始化RedisCacheManager
        RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
        return cacheManager;
    }
}
RedisConfig

 4、在對應的類上指定對應的RedisCacheManager,類似下圖

&n