默认的缓存配置
在诸多的缓存自动配置类中, 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());
- }
删除:
同样, 先删除数据库中的数据, 再删除缓存
来源: https://www.cnblogs.com/ZhuChangwu/p/11379521.html