知方号

知方号

spring cache 学习

1. 功能说明

除了填充缓存,spring cache 也支持使用 @CacheEvict 来删除缓存。@CacheEvict 就是一个触发器,在每次调用被它注解的方法时,就会触发删除它指定的缓存的动作。跟 @Cacheable 和 @CachePut 一样,@CacheEvict 也要求指定一个或多个缓存,也指定自定义一的缓存解析器和 key 生成器,也支持指定条件(condition 参数)。

关于这些(value、cacheNames、key、keyGenerator、cacheManager、cacheResolver、condition)属性参数的用法和说明,请参考:

spring cache 学习 —— @Cacheable 使用详解

 

2. 例子

我们来举个简单的例子,首先看一下 mysql 数据,我们想要删除 name=“微服务测试0修改一下试试” 这条数据(逻辑删除)

 

 

 这条数据在 redis 中已经存在了

 

 

 接下来我们编写一个删除方法,并使用 @CacheEvict 注解:

@Override @CacheEvict(value = "menuById", key = "#id") public Boolean deleteById(String id) { return this.removeById(id); }

然后在 controller 调用它:

@DeleteMapping("/deleteById/{id}") public Boolean deleteById(@PathVariable("id")String id){ return menuService.deleteById(id); }

接下来,我们请求一下接口,看看结果是否删除成功的:

 

 

 再看看 mysq 数据的 del 字段和 redis 是否还有数据:

 

 

 

 

 

 可以看到,mysql 和 缓存都删除成功了。

 

3. allEntries 参数

allEntries 是 @CacheEvict 特有的一个属性,意为是否删除整个缓存(value 或 cacheNames 指定的),默认为 false。从上述的例子中,我们可以看到,结果只删除了指定 key 的缓存数据条目,另一个没有被删除。现在我们来验证这个参数。

首先,我们使用 @Cacheable 或者 @CachePut 让缓存产生两条数据:

 

 

 接下来,我们将上述删除方法的 allEntries=true,再请求一遍删除接口,结果:

 可以看到,尽管我的接口只指定一条数据删除,而且 mysql 中也确实只删除了一条数据,但是缓存中整个都被删除了,说明 allEntries 起作用了。

 

4. beforeInvocation 参数

beforeInvocation 是 @CacheEvict 中特有的一个属性,意为是否在执行对应方法之前删除缓存,默认 false(即执行方法之后再删除缓存)。首先思考一个问题,在什么情况下我们需要主动去删除缓存呢?一般来讲都是在删除数据的时候,需要主动去删除缓存。那么就存在一个问题,程序执行时顺序的,那我们到底是应该先删除缓存,再调用方法去数据库中删除;还是先从数据库中删除,完了之后再去删除对应的缓存呢?在正常情况下,这两种方式差别并不大,毕竟程序执行都是毫秒级的,顺序执行没有什么时间跨度。但是,现实环境复杂,缓存访问和 db 访问都可能会出现异常,这种情况下就有区别了:

如果先删除缓存成功,然后 db 删除失败,那么接下来的查询就会直达数据库,造成压力;如果先 db 删除成功,然后删除缓存失败,那么就会造成脏缓存;

至于该如何取舍,spring cache 通过 beforeInvocation 给开发者提供选择。

接下来,我们来模拟这两种情况,直观地感受一下 beforeInvocation=true 或 false 的区别。虽然我的应用是在本地执行,但是 mysql 和 redis 都是远程服务器上的,所以可以通过断开网络连接的方式来模拟访问异常的情况。

首先让缓存中产生两条数据

对应的 mysql 数据:

 

 

 

我们利用线程的 sleep 方法来延长执行时间,以便来得及进行断开网络的操作。。。。。。。。。。

4.1. beforeInvocation=false 时,预期的结果应该是:mysql 中 del 变为了 true,但是缓存中仍然有数据。

首先看下代码:

@Override @CacheEvict(value = "menuById", key = "#id") public Boolean deleteById(String id) { System.out.println("开始操作 db"); Boolean result = this.removeById(id); System.out.println("db 操作结束"); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } return result; }

接下来,我们请求接口删除 name=“微服务测试0修改一下试试”这条数据。

因为中途关闭了网络,所以返回是这样的:

 

 

 打印是这样的:

 

 

 数据库结果,已经删除:

 

 

 缓存结果,没有删除:

 

 

 结果符合预期,说明当 beforeInvocation = false 时,是先执行方法,再操作缓存。

4.2. beforeInvocation = true 时,预期的结果应该是:mysql 中 del 仍然为 false,但是缓存中已经被删除。

首先看下代码:

@Override@CacheEvict(value = "menuById", key = "#id", beforeInvocation = true)public Boolean deleteById(String id) { System.out.println("开始操作 db"); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } Boolean result = this.removeById(id); System.out.println("db 操作结束"); return result;}

接下来,我们请求接口删除 name=“微服务测试2” 这条数据。

返回是这样的:

 

 

 打印是这样的:

 

 

 数据库结果,没有删除:

 

 

缓存结果,已经删除:

 

 

 

 结果符合预期,说明当 beforeInvocation = true 时,是先操作缓存,再执行方法。

验证成功。

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至lizi9903@foxmail.com举报,一经查实,本站将立刻删除。