揭开 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,这样,在方法执行前我们的缓存就被清空了。可以确保缓存被清空。
执行相同的测试代码, 第一次和第三次都从数据库取数据了,缓存清空有效。
领取专属 10元无门槛券
私享最新 技术干货