SpringBoot 缓存模块

  • 2019 年 10 月 3 日
  • 筆記

默认的缓存配置

在诸多的缓存自动配置类中, SpringBoot默认装配的是SimpleCacheConfigguration, 他使用的CacheManagerCurrentMapCacheManager, 使用 CurrentMap当底层的数据结构,按照Cache的名字查询出Cache, 每一个Cache中存在多个k-v键值对,缓存值

几个主要的概念&常用缓存注解

名称 解释
Cache 缓存接口,主要实现由 RedisChache, EhCacheCachem , ConcurrentMapCache
CacheManager 缓存管理器,管理存放着不同类型的缓存 Cache 组件
@Cacheable 加在方法上,根据方法的请求参数对结果进行缓存
@CacheEvict 清空缓存
@CachePut 保证方法被调用,又希望对方法的结果进行缓存
@EnableCaching 添加在启动类上,表示开始缓存
@keyGenerator 缓存数据时key生成策略
serialize 混存数据时,value的序列化策略

@Cacheable

上手使用

第一步:
开启基于注解的缓存 @EnableCaching

第二步:
使用缓存注解@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;  }
  • value 和 cacheNames 作用一样,都是在指定缓存的名字, 接收一个数组,可以指定多个缓存
  • key, 指定当前这条数据在缓存中的唯一标识,支持SPEL表达式,默认是方法的参数值

    最好都提前确定好使用哪个key

  • keyGenerator, 指定key的生成策略
// 自定义key的生成器    @Configuration  public class MyCacheConfig {      @Bean("myKeyGenerator")      public KeyGenerator keyGenerator() {          return new KeyGenerator() {              @Override              public Object generate(Object o, Method method, Object... objects) {                  return method.getName() + "[" + Arrays.asList(objects).toString() + "]";              }          };      }  }    // 使用  @Cacheable(cacheNames = "dept",key = "#id",keyGenerator = "myKeyGenerator")

一般key和keyGenerator二选一就行

  • cacheManager, 指定缓存管理器
  • cacheResolver, 指定缓存解析器
  • condition, 当条件为ture时, 进行缓存支持SPEL表达式
当id>0时,缓存  @Cacheable(cacheNames = "dept",key = "#id",condition = "#id>0")  使用and添加更多的条件  @Cacheable(cacheNames = "dept",key = "#id",condition = "#id>0 and #id<10")
  • unless, 当条件为true时, 不进行缓存支持SPEL表达式
当结果为空时,不缓存  @Cacheable(cacheNames = "dept",key = "#id",unless="#result == null")
  • sync, 异步缓存

    异步模式下,不支持 unless

执行流程&时机

@Cacheable标注的方法在执行之前,会先去检查缓存中有没有这个数据, 根据这种对应关系查询 key->value, key是使用keyGenerator生成的: 它的默认实现是SimpleKeyGenerate

参数个数 key
没有参数 new SimpleKey()
有一个参数 参数值
多个参数 new SimpleKey(多个参数)

常用的SPEL表达式

描述 示例
当前被调用的方法名 #root.mathodName
当前被调用的方法 #root.mathod
当前被调用的目标对象 #root.target
当前被调用的目标对象类 #root.targetClass
当前被调用的方法的参数列表 #root.args[0] 第一个参数, #root.args[1] 第二个参数…
根据参数名字取出值 #参数名, 也可以使用 #p0 #a0 0是参数的下标索引
当前方法的返回值 #result

@CachePut

调用时机:

目标方法执行完之后生效, @Cache被使用于修改操作比较多,哪怕缓存中已经存在目标值了,但是这个注解保证这个方法依然会执行,执行之后的结果被保存在缓存中

常用更新操作,前端会把id+实体传递到后端使用,我们就直接指定方法的返回值从新存进缓存时的key="#id", 如果前端只是给了实体,我们就使用key="#实体.id" 获取key. 同时,他的执行时机是目标方法结束后执行, 所以也可以使用 key="#result.id", 拿出返回值的id

@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;  }
  • 同样,key的默认值就是参数的id的值,也可以手动指定
  • condition, 指定条件
  • value锁定Cache组件
  • allEntries 指定是否删除当前缓存组件中的全部值,默认是flase不全部删除
  • beforeInvocation, 缓存的清除,是否在方法之前执行, 默认是flase, 表示在方法执行之后执行

如果是在方法执行之前就清空缓存了, 然后方法执行过程中出现异常被中断,缓存依然会被清除

@CacheEvict(value="",key="")  public void deleteById(Integer id){      // todo  }

@Caching

@Target({ElementType.METHOD, ElementType.TYPE})  @Retention(RetentionPolicy.RUNTIME)  @Inherited  @Documented  public @interface Caching {      Cacheable[] cacheable() default {};        CachePut[] put() default {};        CacheEvict[] evict() default {};  }

这是个组合注解,适用于更复杂的情况, 添加在方法上,使用:

@Caching(          cacheable={@Cacheable(...),@Cacheable(...)  }          put={@CachePut(...),@CachePut(...)  }      )  public void xxx(XXX){...}

@CacheConfig

使用场景, 比如,在一个部门的控制器中, 所有的缓存使用的value都是一样的,所有的方法又不能不写,于是使用@CacheConfig简化开发

@Target({ElementType.TYPE})  @Retention(RetentionPolicy.RUNTIME)  @Documented  public @interface CacheConfig {      String[] cacheNames() default {};        String keyGenerator() default "";        String cacheManager() default "";        String cacheResolver() default "";  }

添加在类头上

  • cacheNames , 指定缓存使用的公共的名字, 使用在标注在类头上, 类中方法上的缓存相关的注解都可以不写value="XXX"

整和其他混存中间件

整合Redis当缓存中间件

引入启动器

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

当我们添加进redis相关的启动器之后, SpringBoot会使用RedisCacheConfigratioin当做生效的自动配置类进行缓存相关的自动装配,容器中使用的缓存管理器是
RedisCacheManager, 这个缓存管理器创建的Cache为 RedisCache, 进而操控redis进行数据的缓存

使用RedisTemplate,默认保存进去的数据 k-v 全是Obeject, 被保存的对象需要实现序列化接口, 虽然可以达到缓存的目的,但是对象被序列化成一堆看不懂的乱码, 需要我们自定义Redis 的 Template, 以及自定义CacheManager, 但是这样的话相对于它默认的配置就变的异常的麻烦;


使用redisTemplate做缓存的常用方式

查询:

  • 首先去redis中尝试获取,如果有redis中有值,直接返回redis中的结果 , 如果没有,去数据库中查询,把结果存入redis
// todo 使用redis做缓存,减少和数据库的接触次数  public Label findById(Long labelId) {        // 先尝试从缓存中查询当前对象      Label label = (Label) redisTemplate.opsForValue().get("label_id" + labelId);        if (label==null){          // 从数据库中查询            // 将查出的结果存进缓存中          redisTemplate.opsForValue().set("label_id"+label.getId(),label);      }      return label;

修改:

先更新数据库, 然后删除redis中对应的缓存

public void update(Long labelId, Label label) {  label.setId(labelId);  Label save = labelRepository.save(label);    // todo 数据库修改成功后, 将缓存删除  redisTemplate.delete("label_id"+save.getId());  }

删除:

同样,先删除数据库中的数据, 再删除缓存