默认的缓存配置#
在诸多的缓存自动配置类中, SpringBoot默认装配的是SimpleCacheConfigguration
, 他使用的CacheManager
是 CurrentMapCacheManager
, 使用 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()); }
删除:#
同样,先删除数据库中的数据, 再删除缓存