SpringBoot 整合快取Cacheable實戰詳細使用

前言

我知道在介面api項目中,頻繁的調用介面獲取數據,查詢資料庫是非常耗費資源的,於是就有了快取技術,可以把一些不常更新,或者經常使用的數據,快取起來,然後下次再請求時候,就直接從快取中獲取,不需要再去查詢數據,這樣可以提供程式性能,增加用戶體驗,也節省服務資源浪費開銷,

springboot幫你我們做好了整合,有對應的場景啟動器start,我們之間引入使用就好了,幫我們整合了各種快取

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
    </dependencies>

簡介

快取介紹

Spring 從 3.1 開始就引入了對 Cache 的支援。定義了 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 介面來統一不同的快取技術。並支援使用 JCache(JSR-107)註解簡化我們的開發。

其使用方法和原理都類似於 Spring 對事務管理的支援。Spring Cache 是作用在方法上的,其核心思想是,當我們在調用一個快取方法時會把該方法參數和返回結果作為一個鍵值對存在快取中。

Cache 和 CacheManager 介面說明

Cache 介面包含快取的各種操作集合,你操作快取就是通過這個介面來操作的。
Cache 介面下 Spring 提供了各種 xxxCache 的實現,比如:RedisCache、EhCache、ConcurrentMapCache

CacheManager 定義了創建、配置、獲取、管理和控制多個唯一命名的 Cache。這些 Cache 存在於 CacheManager 的上下文中。

小結

每次調用需要快取功能的方法時,Spring 會檢查指定參數的指定目標方法是否已經被調用過,如果有就直接從快取中獲取方法調用後的結果,如果沒有就調用方法並快取結果後返回給用戶。下次調用直接從快取中獲取。

使用Spring快取抽象時我們需要關注以下兩點;

  1. 確定方法需要被快取以及他們的快取策略
  2. 從快取中讀取之前快取存儲的數據

快速開始

  1. 使用快取我們需要開啟基於註解的快取,使用 @EnableCaching 標註在 springboot 主啟動類上或者配置類上
@SpringBootApplication
@MapperScan(value = {"cn.soboys.kmall.mapper","cn.soboys.kmall.sys.mapper","cn.soboys.kmall.security.mapper"},nameGenerator = UniqueNameGenerator.class)
@ComponentScan(value =  {"cn.soboys.kmall"},nameGenerator = UniqueNameGenerator.class)
@EnableCaching  //開啟快取註解驅動,否則後面使用的快取都是無效的
public class WebApplication {
    private static ApplicationContext applicationContext;

    public static void main(String[] args) {
        applicationContext =
                SpringApplication.run(WebApplication.class, args);
        //displayAllBeans();
    }


    /**
     * 列印所以裝載的bean
     */
    public static void displayAllBeans() {
        String[] allBeanNames = applicationContext.getBeanDefinitionNames();
        for (String beanName : allBeanNames) {
            System.out.println(beanName);
        }
    }
}

或者配置類

/**
 * @author kenx
 * @version 1.0
 * @date 2021/8/17 15:05
 * @webSite //www.soboys.cn/
 * 自定義快取配置
 */
@Configuration
@Slf4j
@EnableCaching  //開啟快取註解驅動,否則後面使用的快取都是無效的
public class CacheConfig {

    //自定義配置類配置keyGenerator

    @Bean("myKeyGenerator")
    public KeyGenerator keyGenerator(){
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                return method.getName()+"["+ Arrays.asList(params).toString() +"]";
            }
        };
    }
}
  1. 標註快取註解在需要快取的地方使用@Cacheable註解
@CacheConfig(cacheNames = "menuCache",keyGenerator ="myKeyGenerator" )
public interface IMenuService extends IService<Menu> {
    /**
     * 獲取用戶菜單資訊
     *
     * @param username 用戶名
     * @return
     */
    @Cacheable(key = "#username")
    List<Menu> getUserMenus(String username);
}

@Cacheable註解有如下一些參數我們可以看到他源碼

@Target({ElementType.TYPE, ElementType.METHOD})
@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;
}

下面介紹一下 @Cacheable 這個註解常用的幾個屬性:

  1. cacheNames/value :指定快取組件的名字;將方法的返回結果放在哪個快取中,是數組的方式,可以指定 多個快取;

  2. key :快取數據時使用的 key,可以用它來指定。默認是使用方法參數的值。(這個 key 你可以使用 spEL 表達式來編寫如 #i d;參數id的值 #a0 #p0 #root.args[0]

  3. keyGenerator :key的生成器;可以自己指定key的生成器的組件id 然後key 和 keyGenerator 二選一使用

  4. cacheManager :可以用來指定快取管理器。從哪個快取管理器裡面獲取快取。或者cacheResolver指定獲取解析器

  5. condition :可以用來指定符合條件的情況下才快取

condition = "#id>0"
condition = "#a0>1":第一個參數的值》1的時候才進行快取
  1. unless :否定快取。當 unless 指定的條件為 true ,方法的返回值就不會被快取。當然你也可以獲取到結果進行判斷。(通過 #result 獲取方法結果)
unless = "#result == null"
unless = "#a0==2":如果第一個參數的值是2,結果不快取;
  1. sync :是否使用非同步模式。非同步模式的情況下unless不支援 默認是方法執行完,以同步的方式將方法返回的結果存在快取中

cacheNames/value屬性

用來指定快取組件的名字,將方法的返回結果放在哪個快取中,可以是數組的方式,支援指定多個快取

 /**
     * 獲取用戶菜單資訊
     *
     * @param username 用戶名
     * @return
     */
    @Cacheable(cacheNames = "menuCache") 或者// @Cacheable(cacheNames = {"menuCache","neCacge"})
    List<Menu> getUserMenus(String username);

如果只有一個屬性,cacheNames可忽略,直接是value屬性默認

key

快取數據時使用的 key。默認使用的是方法參數的值。可以使用 spEL 表達式去編寫。

Cache SpEL available metadata

名稱 位置 描述 示例
methodName root對象 當前被調用的方法名 #root.methodname
method root對象 當前被調用的方法 #root.method.name
target root對象 當前被調用的目標對象實例 #root.target
targetClass root對象 當前被調用的目標對象的類 #root.targetClass
args root對象 當前被調用的方法的參數列表 #root.args[0]
caches root對象 當前方法調用使用的快取列表 #root.caches[0].name
argumentName 執行上下文(avaluation context) 當前被調用的方法的參數,如findArtisan(Artisan artisan),可以通過#artsian.id獲得參數 #artsian.id
result 執行上下文(evaluation context) 方法執行後的返回值(僅當方法執行後的判斷有效,如 unless cacheEvict的beforeInvocation=false) #result
//key = "#username" 就是參數username
 @Cacheable(key = "#username" ,cacheNames = "menuCache")
List<Menu> getUserMenus(String username);

keyGenerator

key 的生成器,可以自己指定 key 的生成器,通過這個生成器來生成 key

定義一個@Bean類,將KeyGenerator添加到Spring容器

@Configuration
@Slf4j
@EnableCaching  //開啟快取註解驅動,否則後面使用的快取都是無效的
public class CacheConfig {

    //自定義配置類配置keyGenerator

    @Bean("myKeyGenerator")
    public KeyGenerator keyGenerator(){
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                return method.getName()+"["+ Arrays.asList(params).toString() +"]";
            }
        };
    }
}

在使用指定自己的@Cacheable(cacheNames = "menuCache",keyGenerator ="myKeyGenerator" )

注意這樣放入快取中的 key 的生成規則就按照你自定義的 keyGenerator 來生成。不過需要注意的是:@Cacheable 的屬性,key 和 keyGenerator 使用的時候,一般二選一。

condition

符合條件的情況下才快取。方法返回的數據要不要快取,可以做一個動態判斷。

 /**
     * 獲取用戶菜單資訊
     *
     * @param username 用戶名
     * @return
     */
    //判斷username 用戶名是kenx開頭才會被快取
    @Cacheable(key = "#username" ,condition = "#username.startsWith('kenx')")
    List<Menu> getUserMenus(String username);

unless

否定快取。當 unless 指定的條件為 true ,方法的返回值就不會被快取。

 /**
     * 獲取用戶菜單資訊
     *
     * @param username 用戶名
     * @return
     */
    //判斷username 用戶名是kenx開頭不會被快取
    @Cacheable(key = "#username" ,condition = "#username.startsWith('kenx')")
    List<Menu> getUserMenus(String username);

spEL 編寫 key

當然我們可以全局去配置,cacheNames,keyGenerator屬性通過@CacheConfig註解可以用於抽取快取的公共配置,然後在類加上就可以,eg:如

//全局配置,下面用到快取方法,不配置默認使用全局的
@CacheConfig(cacheNames = "menuCache",keyGenerator ="myKeyGenerator" )
public interface IMenuService extends IService<Menu> {
    /**
     * 獲取用戶菜單資訊
     *
     * @param username 用戶名
     * @return
     */
    @Cacheabl
    List<Menu> getUserMenus(String username);
}

深入使用

@CachePut

@CachePut註解也是一個用來快取的註解,不過快取和@Cacheable有明顯的區別是即調用方法,又更新快取數據,也就是執行方法操作之後再來同步更新快取,所以這個主鍵常用於更新操作,也可以用於查詢,主鍵屬性和@Cacheable有很多類似的參看 @CachePut源碼

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CachePut {
    @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 "";
}
/**
     *  @CachePut:既調用方法,又更新快取數據;同步更新快取
     *  修改了數據,同時更新快取
     */
    @CachePut(value = {"emp"}, key = "#result.id")
    public Employee updateEmp(Employee employee){
        employeeMapper.updateEmp(employee);
        LOG.info("更新{}號員工數據",employee.getId());
        return employee;
    }

@CacheEvict

清空快取
主要屬性:

  1. key:指定要清除的數據
  2. allEntries = true:指定清除這個快取中所有的數據
  3. beforeInvocation = false:默認代表快取清除操作是在方法執行之後執行
  4. beforeInvocation = true:代表清除快取操作是在方法運行之前執行
@CacheEvict(value = {"emp"}, beforeInvocation = true,key="#id")
    public void deleteEmp(Integer id){
        employeeMapper.deleteEmpById(id);
        //int i = 10/0;
    }

@Caching

@Caching 用於定義複雜的快取規則,可以集成@Cacheable和 @CachePut

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

@CacheConfig

@CacheConfig註解可以用於抽取快取的公共配置,然後在類加上就可以

//全局配置,下面用到快取方法,不配置默認使用全局的
@CacheConfig(cacheNames = "menuCache",keyGenerator ="myKeyGenerator" )
public interface IMenuService extends IService<Menu> {
    /**
     * 獲取用戶菜單資訊
     *
     * @param username 用戶名
     * @return
     */
    @Cacheable(key = "#username" )
    List<Menu> getUserMenus(String username);
}

參考

  1. SpringBoot之快取使用教程
  2. 快取入門