一、简述

  1. 3.1 开始,Spring 引入了对 Cache 的支持。其使用方法和原理都类似于 [Spring 对事务管理](https://www.jianshu.com/p/7e76ce65e3ad)的支持。Spring Cache 是作用在方法上的,其核心思想:当调用一个缓存方法时,会把该方法参数和返回结果作为一个键值对存放在缓存中,等到下次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。所以在使用 Spring Cache 的时候,要保证缓存的方法对于相同的方法参数要有相同的返回结果。使用 Spring Cache 需要做到两点:<br /> 1️⃣在 Springboot 的启动类上使用 @EnableCaching 开启缓存。<br /> 2️⃣声明某些方法使用缓存。<br />注意: Spring 对事务管理的支持一样,Spring 对 Cache 的支持也有基于注解和基于 XML 配置两种方式。

二、基于注解的支持

   其核心主要是 @Cacheable 和 @CacheEvict。使用 @Cacheable 标记的方法在执行后,Spring Cache 将缓存其返回结果,而使用 @CacheEvict 标记的方法会在方法执行前或者执行后移除 Spring Cache 中的某些元素。

1️⃣@Cacheable:可以标记在类上或者方法上。
当标记在方法上时表示该方法是支持缓存的,当标记在类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring 会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。Spring 在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring 又支持两种策略,默认策略和自定义策略。需要注意的是当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的。@Cacheable 可以指定三个属性,value、key 和 condition。
SpringBoot 缓存之 @Cacheable 详细介绍 - 图1【value 属性指定 Cache 名称】
value 是必须指定的,其表示当前方法的返回值被缓存在哪个 Cache 上,对应 Cache 的名称。其可以是一个 Cache 也可以是多个 Cache,当需要指定多个 Cache 时其是一个数组。

@Cacheable("cache1")//Cache是发生在cache1上的
public User find(Integer id) {
    return null;
}
@Cacheable({"cache1", "cache2"})//Cache是发生在cache1和cache2上的
public User find(Integer id) {
    return null;
}

使用 key 属性自定义 key】
key 属性是用来指定 Spring 缓存方法的返回结果时对应的 key 的。该属性支持 SpringEL 表达式。当没有指定该属性时,Spring 将使用默认策略生成 key。
自定义策略是指可以通过 Spring 的 EL 表达式来指定 key。这里的 EL 表达式可以使用方法参数及它们对应的属性。使用方法参数时可以直接使用#参数名或者#p参数index。如下:

//key是指传入时的参数
@Cacheable(value="users", key="#id")
public User find(Integer id) {
    return null;
}
// 表示第一个参数
@Cacheable(value="users", key="#p0")
public User find(Integer id) {
    return null;
}
// 表示User中的id值
@Cacheable(value="users", key="#user.id")
public User find(User user) {
    return null;
}
// 表示第一个参数里的id属性值
@Cacheable(value="users", key="#p0.id")
public User find(User user) {
   return null;
}
   除了上述使用方法参数作为 key 之外,Spring 还提供了一个 root 对象可以用来生成 key。通过该 root 对象可以获取到以下信息。<br />![](https://cdn.nlark.com/yuque/0/2022/webp/22535347/1652024826034-aa5e9575-d359-47c0-a7fd-5848e29b2c0a.webp#clientId=uad485833-028e-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u96e58b21&margin=%5Bobject%20Object%5D&originHeight=422&originWidth=1107&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u589cd189-3bc9-4f08-8ada-9144e6d2b69&title=)        当要使用 root 对象的属性作为 key 时,也可以将“#root”省略,因为 Spring 默认使用的就是 root 对象的属性。如:
// key值为:user中的name属性的值
@Cacheable(value={"users", "xxx"}, key="caches[1].name")
public User find(User user) {
    return null;
}

【condition 属性指定发生的条件】
有时候可能并不希望缓存一个方法的所有返回结果。condition 属性默认为空,表示将缓存所有的调用情形。其值是通过 SpringEL 表达式来指定的,当为 true 时表示进行缓存处理;当为 false 时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。如下表示只有当 user 的 id 为偶数时才会缓存:

// 根据条件判断是否缓存
@Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")
public User find(User user) {
   System.out.println("find user by user " + user);
   return user;
}

2️⃣@CachePut
在支持 Spring Cache 的环境下,对于使用 @Cacheable 标注的方法,Spring 在每次执行前都会检查 Cache 中是否存在相同 key 的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。@CachePut 也可以声明一个方法支持缓存功能。与 @Cacheable 不同的是使用 @CachePut 标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

//@CachePut也可以标注在类或方法上。可以指定的属性跟@Cacheable是一样的
@CachePut("users")//每次都会执行方法,并将结果存入指定的缓存中
public User find(Integer id) {
   return null;
}

3️⃣@CacheEvict
@CacheEvict 是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。@CacheEvict 可以指定的属性有 value、key、condition、allEntries 和 beforeInvocation。其中 value、key 和 condition 的语义与 @Cacheable 对应的属性类似。即 value 表示清除操作是发生在哪些 Cache 上的(对应 Cache 的名称);key 表示需要清除的是哪个 key,如未指定则会使用默认策略生成的 key;condition 表示清除操作发生的条件。
【allEntries 属性】
allEntries 是 boolean 类型,表示是否需要清除缓存中的所有元素。默认为 false,表示不需要。当指定了 allEntries 为 true 时,Spring Cache 将忽略指定的 key。有的时候需要 Cache 一下清除所有的元素,这比一个一个清除元素更有效率。

@CacheEvict(value="users", allEntries=true)
public void delete(Integer id) {
   System.out.println("delete user by id: " + id);
}

【beforeInvocation 属性】
清除操作默认是在对应方法成功执行之后触发的,如果方法抛出异常而未能成功返回时不会触发清除操作。使用beforeInvocation 可以改变触发清除操作的时间,当指定该属性值为 true 时,Spring 会在调用该方法之前清除缓存中的指定元素。

@CacheEvict(value="users", beforeInvocation=true)
public void delete(Integer id) {
   System.out.println("delete user by id: " + id);
}
  其实除了使用 @CacheEvict 清除缓存元素外,当使用 Ehcache 作为实现时,也可以配置 Ehcache 自身的清除策略,其是通过 Ehcache 的配置文件来指定的。<br />4️⃣@Caching<br />     @Caching 注解可以在一个方法或者类上同时指定多个 Spring Cache 相关的注解。其拥有三个属性:cacheable、put 和 evict,分别用于指定 @Cacheable、@CachePut 和 @CacheEvict。
@Caching(cacheable = @Cacheable("users"), evict = { @CacheEvict("cache2"),
@CacheEvict(value = "cache3", allEntries = true) })
public User find(Integer id) {
   return null;
}


5️⃣使用自定义注解
Spring 允许在配置可缓存的方法时使用自定义的注解,前提是自定义的注解上必须使用对应的注解进行标注。如有如下这么一个使用 @Cacheable 进行标注的自定义注解:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Cacheable(value="users")
public @interface MyCacheable {
}
  那么在需要缓存的方法上使用 @MyCacheable 进行标注也可以达到同样的效果。
@MyCacheable
public User findById(Integer id) {
    System.out.println("find user by id: " + id);
    User user = new User();
    user.setId(id);
    user.setName("Name" + id);
    return user;
}

SpringBoot 缓存之 @Cacheable 详细介绍 - 图2

三、缓存初始化前配置

         使用此注解的话需要对该注解@bean的配置进行一个配置
package com.orange.springcacheredis.config;

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        return RedisCacheManager.builder(factory)
                .cacheDefaults(defaultCacheConfig(20))
                .withInitialCacheConfigurations(initCacheConfigMap())
                .transactionAware()
                .build();
    }

    private RedisCacheConfiguration defaultCacheConfig(Integer second) {
        GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(second))
                .serializeKeysWith(RedisSerializationContext
                        .SerializationPair
                        .fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext
                        .SerializationPair
                        .fromSerializer(serializer))
                .disableCachingNullValues();
    }

    /**
    *个人配置key的过期时间
    */
    private Map<String, RedisCacheConfiguration> initCacheConfigMap() {
        Map<String, RedisCacheConfiguration> map = new HashMap<>(16);
        map.put("emp", this.defaultCacheConfig(100));
        map.put("emp-hot", this.defaultCacheConfig(1000));
        map.put("dep", this.defaultCacheConfig(1000));
        return map;
    }

}

四、应用场景

1️⃣@CachePut:更新数据库的数据并更新缓存

  1. 先调用更新数据库方法
  2. 再将更新的数据写入名为(users)缓存中

    @PostMapping(value = "/updateUserInfo")
    @CachePut(value = "users", key = "#user.userId")
    public void updateUserInfo(User user) {
    userService.updateUserInfo(user);
    }
    

    2️⃣@Cahceable:查询出的数据是最新的而不是之前缓存中的

  3. 这里 value 属性的值同 @CachePut 时的 value 值一样,确保查询与更新都是同一块缓存。

  4. 此时查询的 key 要和更新的 key 保持一致,确保拿到的是更新后的数据。
@GetMapping(value = "/queryUserInfo/{userId}")
@Cacheable(value = "users", key = "#userId")
public User queryUserInfo(@PathVariable("userId")Integer userId) {
   userService.queryUserInfo(userId);
}