SpingBoot-简单缓存

版权声明:来一来,看一看,有钱的捧个人场,没钱的你不得捧个人场 https://blog.csdn.net/wait_for_eva/article/details/82962217

缓存场景

1. 重复使用

​ 频繁使用数据,为了避免多次查询数据库,浪费时间、占用资源、造成压力,应使用缓存。

应该对查询数据作特殊限制,防止缓存击穿

2. 暂缓数据

​ 中间数据,存在一段时间,但是不必持久化,使用缓存更方便。

在生产环境中,产生的中间数据在一段时间内可复用,避免重复计算造成不必要的开销。

即使是上述的基础数据,即使使用缓存,为了避免更新无法生效的情况,也应该设置过期时间。

从这方面考虑,两者的体现一致,只是数据来源存在差异而已。

JSR107

接口 作用
CachingProvider 管理CacheManager
CacheManager 管理Cache
Cache 管理Entry
Entry key-value存储缓存数据
Expiry 设置Entry过期时间
  • 管理含义包括创建,配置,获取等,完全控制下一级
  • 上对下为一对多,下对上为一对一

Spring-Boot缓存接口

组件 作用
Cache 缓存接口
CacheManager 管理Cache
Cacheable 标记方法,对返回结果进行缓存
CacheEvict 清空缓存
CachePut 保证方法调用,且希望结果被缓存
EnableCaching 开启基于注解的缓存
keyGenerator 缓存key生成策略
serialize 缓存value序列化策略

@EnableCaching

@MapperScan("com.godme.cache.mapper")
@SpringBootApplication
@EnableCaching
public class GodmeApplication {
	public static void main(String[] args) {
		SpringApplication.run(GodmeApplication.class, args);
	}
}

@EnableCaching:必须先声明@EnableCaching开启,才能使用缓存注解

@Cacheable

@Component
public class StudentDao {
   
    StudentMapper studentMapper;
    
    @Cacheable
    public Student queryStudent(Integer id){
        System.err.println("查询");
        return studentMapper.getStudent(id);
    }
}

标记方法,会把运行结果缓存起来。

  • cacheNames/value

指定缓存名字

@Cacheable(cacheNames = {"a", "b"} )

支持复数个名称

  • key

缓存都是key-value,这就是其中的key

扫描二维码关注公众号,回复: 3545803 查看本文章

不指定的情况下默认使用的是方法参数id(不是参数名id,而是id的具体取值)

指定时可以是具体字符串,也可以是spEL表达式

@Cacheable(key = "#{methodName} + '[' + #{id} + ']'")
属性 示例 描述
methodName #root.methodName 方法名称
method #root.method.name 方法对象
target #root.target 目标对象
targetClass #root.targetClass 目标类
args #root.args[0] 参数列表,数组
caches #root.caches[0].name 缓存列表,cacheNames数组
args-name #a0,#p0 参数名称,下标指定
args,param
result #result 方法返回值
  • keyGenerator
  • key到的生成器
  • keykeyGenerator冲突,二选一使用,优先使用key
// 自定义KeyGenerator
@Configuration
public class MyConfig {
    @Bean("godmeKeygenerator")
    public KeyGenerator keyGenerator(){
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                return method.getName() + "["+ Arrays.asList(params).toString()+"]";
            }
        };
    }
}
// 指定keyGenerator
@Cacheable(keyGenerator = "godmeKeyGenerator")

  • cacheManager

指定cacheManger,把值存入或从指定cacheManager获取

  • cacheResolver
  • 缓存解析器,同cacheManager,管理缓存集合
  • cacheManagercacheResolver二选一
  • 缓存集合具体看底层实现,默认使用currentMap
  • condition
  • 指定条件才进行缓存
  • 支持spEL表达式
@Cacheable(condition = "#id>0")
  • unless
  • 同condition,操作相反,满足条件则不缓存
@Cacheable(unless = "#id<0")
  • 可获取结果判断
@Cacheable(unless = "#result == null")
  • sync

是否采用异步模式,true, false

@Cacheable(sync = true)
  • 感情纠葛
  • keyKeyGenerator二取其一
  • conditionunless含义相反
  • 默认缓存
  • contdition第一层过滤,筛选需要缓存的
  • unless二次过滤,没有condition相当于第一层,满足条件的不缓存,即使满足condition
  • syncunless不共存,使用了synctrue),不能设置 unless(不生效)

@CachePut

    @CachePut(cacheNames = "student", key = "#{student.id}")
    public int updateStudent(Student student){
        System.err.println("更新");
        return studentMapper.updateStudent(student);
    }
  • 执行顺序
  • @Cacheable

之前说了@Cacheable但是没有执行顺序,但是回顾一下它的作用,就很轻松的明白过来。

缓存执行结果,下次调用不用再执行,而是直接获取缓存。

所以,每次调用之前它都会优先查缓存,没有查到的情况下,执行方法后再缓存。

因此,执行步骤为

  1. 查询缓存
  2. 方法执行(未查询到缓存)
  3. 结果缓存(为查询到缓存)
  4. 结果返回

有缓存,那就是直接的两步,只有缓存不存在时分四步走

  • CachePut

更新缓存,就是它的作用了。

所以是先执行方法,然后再把结果更新到缓存中。

所谓更新也就是没有就添加,有了就替换

因此,执行步骤为

  1. 方法执行
  2. 缓存更新
  3. 结果返回

所以,不论是否存在缓存,始终都是三步走,关键区别在于缓存是新增,还是更新

  • 更新缓存

缓存的确会写入,具体是更新或者新增不用在意。

关键在于缓存的获取

@Cacheable是能获取缓存的,我们更新缓存,主要也是要让缓存能够的准确。

所以,要严格区分key

@CachePut(cacheNames = "student", key = "#{student.id}")

还记得的话,key默认的是参数值,如果key不统一,缓存就会是两个毫不相干的路人。

要确保即使方法不同,参数各异,采取的key也要一致。

保证更新写对了地方,保证查询查对了地方。

  • 其他属性

特殊的就只是#result而已,在条件判断时需要对result进行判断。

如果方法不执行,result是不存在的。

存运行流程上分析,CachePut是完全没问题的。

但是Cacheable在方法第一次执行时,result是不存在的,无缓存情况下,是不能用的。

这点区别要牢记,在Cacheable上尽量别用result

@CacheEvict

    @CacheEvict(key = "#{id}")
    public int deleteStudent(Integer id){
        System.err.println("删除");
        return studentMapper.deleteStudent(id);
    }
  • key

老生常谈,注意key的一致性,别该删的没删,不该删的全没了,那就该跑路了。

  • allEntries

布尔值,默认为false

false的时候,清除缓存是按照key来进行清除的,清除Cache下的单一Entry

true的时候,通过cacheNamesvalue来进行清除,会清除指定名字Cache下的全部Entry

  • beforeInvocation

布尔值,默认false

这个涉及的就是清除顺序了。

清除的时候只和key或者cacheNames相关,对于方法的执行步骤上好像没有依赖性。

默认情况下,在方法执行完毕之后再进行缓存的清除,这就有了一个逻辑的连贯性。

当方法异常或其他情况导致方法中断时,缓存就可能得不到清除。

设置为true,就会在方法之前进行缓存清除,保证缓存的事务完整。

@Caching

    @Cacheable(key = "#{id}")
    @CachePut(key = "#{id}")
    public Student queryStudent(Integer id){
        System.err.println("查询");
        return studentMapper.getStudent(id);
    }

恩~~~,感觉没啥用?

那考虑一下业务场景:我们通过id查询出了一个对象,但是查询办法有好多。

说不定,有通过name查询,通过parentName?motherName?fatherName?

    @Cacheable(key = "#{id}")
    @CachePut(key = "#{parentName,motherName,fatherName}")
    public Student queryStudent(Integer id){
        System.err.println("查询");
        return studentMapper.getStudent(id);
    }

这样一来,只要查询一遍对象,其他方法都可以从缓存里面取出来了,这不是好牛逼了么。

这的确很好用,但是有两个问题:

  1. 如果有不同配置,但是相同注解只能标注一个
  2. 好丑,没有统一管理
    @Cacheable(key = "#{id}")
    @CachePut(value="parent", key = "#{parentName}")
    @CachePut(value="mother", key = "#{motherName}")
    @CachePut(value="father", key = "#{fatherName}")
    public Student queryStudent(Integer id){
        System.err.println("查询");
        return studentMapper.getStudent(id);
    }

@CachePut都是红的,怎么搞?

    @Caching(
            cacheable = {
                    @Cacheable(value = "a", key = "#{id}"),
                    @Cacheable(value = "b", key = "#{name}")
            },
            put = {
                    @CachePut(value = "a", key = "#{id}"),
                    @CachePut(value = "b", key = "#{name}")
            },
            evict = {}
    )
    public Student queryStudent(Integer id){
        System.err.println("查询");
        return studentMapper.getStudent(id);
    }

这样一来,就可以同时配置多个注解了,还能统一管理,美滋滋。

不过我发现一个坑,有缓存的情况下put生效了怎么办,evict呢?

condition引起了我的注意,条件判断什么的这里面都能够完成啊。

不过具体办法加上结合业务逻辑,估计就得靠自己想办法了。

@CacheConfig

@CacheConfig(cacheNames = "student", keyGenerator = "godmeKeyGenerator")
@Component
public class StudentDao {
    ...
    
}

正和上面所提到的一样,写太多太繁杂了,我们要的只是葫芦。

通过CacheconfigClass进行标记,把公共的东西都抽取进来,里面的注解就只用关心条件逻辑了。

public @interface CacheConfig {
	String[] cacheNames() default {};
	String keyGenerator() default "";
	String cacheManager() default "";
	String cacheResolver() default "";
}

可抽取的公共配置都在这了,然后你就不必思考–我打的缓存究竟是在

  • 哪个CacheManager
  • 那个Cache

之类的问题了

恩~~~

  • 最后两个只是管理用的,无涉缓存原理
  • 缓存步骤大同小异,缓存实现千差万别

猜你喜欢

转载自blog.csdn.net/wait_for_eva/article/details/82962217