首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

揭开 Spring boot 缓存注解的神秘面纱(1)

揭开 Spring boot 缓存注解的神秘面纱 系列

Spring 缓存注解以及扩展方法

Spring 缓存注解中使用SpEL

Spring 缓存基础知识

Spring 缓存的生命周期

Spring 缓存管理器以及扩展

Spring 缓存注解中的keyGenerate

Spring boot 之 CacheAutoConfiguration 原理

今天开篇, 后续文章请期待...

Spring 缓存注解以及扩展方法

Spring cache注解包括 @CacheConfig, @Cacheable, @CachePut, @CacheEvict

1. 添加 spring-boot-starter-cache 依赖

使用EhCache等作为缓存

使用redis作为缓存

等等其他,读者举一反三的去了解使用吧,不在赘述。

2. 开启缓存功能

3. 缓存注解

3.1. @Cacheable

Spring 在执行 @Cacheable 标注的方法前先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,执行该方法并将方法返回值放进缓存。

1. 在执行方法之前(#result还拿不到返回值)判断condition,如果返回true,则从缓存查询结果

表示如果满足条件 "#id lt 10" ,则先查询缓存,如果缓存不存在,才执行conditionFindById方法

2. 在执行方法之后(#result已经拿到返回值)判断condition,如果返回true,则将结果存储到缓存

表示如果方法conditionFindById的结果满足条件 "#result.age lt 10" ,则进行缓存,否则不缓存

3. 在执行方法之后(#result已经拿到返回值)判断unless,如果返回true,则结果将不会缓存

表示如果方法conditionFindById的结果满足条件 "#id lt 10" ,则不缓存

4. 在执行方法之后(#result已经拿到返回值)判断unless,如果返回true,则结果将不会缓存

表示如果方法conditionFindById的结果满足条件 "#result.age lt 10" ,则不缓存

3.2. @CachePut

参数和 @Cacheable 类似,但会把方法的返回值放入缓存中, 主要用于数据新增和修改方法。

表示在执行完方法后(#result就能拿到返回值了)判断unless=true,则不缓存;(即跟condition相反)。

3.3. @CacheEvict

方法执行成功后会从缓存中移除相应数据。

3.4. @Caching

有时候我们可能组合多个Cache注解使用;@Caching定义如下:

比如用户新增成功后,我们要添加id-->user;username--->user;email--->user的缓存;此时就需要@Caching组合多个注解标签了。

@Caching的执行流程

流程中需要注意的就是2/3/4步:@CachePut和@Cacheable不能同时存在,如果同时存在,仅执行@CachePut,@Cacheable被直接略过。

3.5. 自定义缓存注解

比如之前的那个@Caching组合,会让方法上的注解显得整个代码比较乱,此时可以使用自定义注解把这些注解组合到一个注解中,如:

这样我们在方法上使用如下代码即可,整个代码显得比较干净。

补充,其实对于:id--->user;username---->user;email--->user;更好的方式可能是:id--->user;username--->id;email--->id;保证user只存一份;如:

4. 重要说明

5. 深入思索

一般来说,我们的更新操作只需要刷新缓存中某一个值,所以定义缓存的key值的方式就很重要,最好是能够唯一,因为这样可以准确的查询和清除特定的缓存,而不会影响到其它缓存值。

单记录的增删改查要不要设缓存呢?我认为与业务相关,如果处理结果会被多次使用,那么一般是建议缓存,反之则不建议

记录集要不要缓存呢?同样要看结果集是否会被经常使用

不建议缓存空值, 使用@Cacheable(condition = "#result != null)

不建议缓存的生命周期设为太大,容易导致缓存长期占据redis服务内存,反而造成瓶颈。

基于 proxy 的 spring aop 带来的内部调用不生效. 同样, 非 public 方法也存在这个问题

@CacheEvict 的可靠性问题: beforeInvocation缺省为 false,即缺省情况下,都是在实际的方法执行完成后,才对缓存进行清空操作。期间如果执行方法出现异常,则会导致缓存清空不被执行。 当然,以上结论也不是绝对的,请视具体情况分析,不要生搬硬套.

对第1点进行补充说明(原文引自张开涛的博文): 如果有一个缓存存放 list,现在你执行了一个 update(user)的方法,你一定不希望清除整个缓存而想替换掉update的元素. 这个在现有的抽象上没有很好的方案. 我认为可以这样改造:

value也是一个SpEL,这样可以定制要缓存的数据;afterCache定制自己的缓存成功后的其他逻辑。

对第6点进行补充说明

上面介绍过 spring cache 的原理,即它是基于动态生成的 proxy 代理机制来对方法的调用进行切面,这里关键点是对象的引用问题,如果对象的方法是内部调用(即 this 引用)而不是外部引用,则会导致 proxy 失效,那么我们的切面就失效,也就是说上面定义的各种注释包括 @Cacheable、@CachePut 和 @CacheEvict 都会失效,我们来演示一下。

上面我们定义了一个新的方法 getAccountByName2,其自身调用了 getAccountByName 方法,这个时候,发生的是内部调用(this),所以没有走 proxy,导致 spring cache 失效. 运行结果

可见,结果是每次都查询数据库,缓存没起作用。要避免这个问题,就是要避免对缓存方法的内部调用,或者避免使用基于 proxy 的 AOP 模式来解决这个问题。例如

其他更多的基于 aspectJ 的 AOP 模式解决方案,我将另文专门描述。

对第7点进行补充说明我们演示一下,@CacheEvict 注释属性 beforeInvocation= false时执行方法出现异常:

上面的测试代码先查询了两次,然后 reload,然后再查询一次,结果应该是只有第一次查询走了数据库,其他两次查询都从缓存,第三次也走缓存因为 reload 失败了。运行结果:

和预期一样。那么我们如何避免这个问题呢?我们可以用 @CacheEvict 注释提供的 beforeInvocation 属性,将其设置为 true,这样,在方法执行前我们的缓存就被清空了。可以确保缓存被清空。

执行相同的测试代码, 第一次和第三次都从数据库取数据了,缓存清空有效。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180610G1FKWV00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券