• Spring系列缓存注解@Cacheable @CacheEvit @CachePut 使用姿势介绍


    SpringBoot系列缓存注解@Cacheable @CacheEvit @CachePut使用姿势介绍

    Spring在3.1版本,就提供了一条基于注解的缓存策略,实际使用起来还是很丝滑的,本文将针对几个常用的注解进行简单的介绍说明,有需要的小伙伴可以尝试一下

    本文主要知识点:

    • @Cacheable: 缓存存在,则使用缓存;不存在,则执行方法,并将结果塞入缓存
    • @CacheEvit: 失效缓存
    • @CachePut: 更新缓存

    I. 项目环境

    1. 项目依赖

    本项目借助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA + redis5.0进行开发

    开一个web服务用于测试

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>
    

    全程使用默认配置,redis本机,端口6379,无密码

    II. 缓存注解介绍

    1. @Cacheable

    这个注解用于修饰方法or类,当我们访问它修饰的方法时,优先从缓存中获取,若缓存中存在,则直接获取缓存的值;缓存不存在时,执行方法,并将结果写入缓存

    这个注解,有两个比较核心的设置

    	/**
    	 * 与 cacheNames 效果等价
    	 */
    	@AliasFor("cacheNames")
    	String[] value() default {};
    
    	
    	@AliasFor("value")
    	String[] cacheNames() default {};
    
    	/**
    	 * 缓存key
    	 */
    	String key() default "";
    

    cacheNames可以理解为缓存key的前缀,可以为组件缓存的key变量;当key不设置时,使用方法参数来初始化,注意key为SpEL表达式,因此如果要写字符串时,用单引号括起来

    一个简单的使用姿势

    /**
     * 首先从缓存中查,查到之后,直接返回缓存数据;否则执行方法,并将结果缓存
     * <p>
     * redisKey: cacheNames + key 组合而成 --> 支持SpEL
     * redisValue: 返回结果
     *
     * @param name
     * @return
     */
    @Cacheable(cacheNames = "say", key = "'p_'+ #name")
    public String sayHello(String name) {
        return "hello+" + name + "-->" + UUID.randomUUID().toString();
    }
    

    如我们传参为 yihuihui, 那么缓存key为 say::p_yihuihui

    除了上面三个配置值之外,查看@Cacheable注解源码的童鞋可以看到还有condition设置,这个表示当它设置的条件达成时,才写入缓存

    /**
     * 满足condition条件的才写入缓存
     *
     * @param age
     * @return
     */
    @Cacheable(cacheNames = "condition", key = "#age", condition = "#age % 2 == 0")
    public String setByCondition(int age) {
        return "condition:" + age + "-->" + UUID.randomUUID().toString();
    }
    

    上面这个case中,age为偶数的时候,才走缓存;否则不写缓存

    接下来是unless参数,从名字上可以看出它表示不满足条件时才写入缓存

    /**
     * unless, 不满足条件才写入缓存
     *
     * @param age
     * @return
     */
    @Cacheable(cacheNames = "unless", key = "#age", unless = "#age % 2 == 0")
    public String setUnless(int age) {
        return "unless:" + age + "-->" + UUID.randomUUID().toString();
    }
    

    2. @CachePut

    不管缓存有没有,都将方法的返回结果写入缓存;适用于缓存更新

    /**
     * 不管缓存有没有,都写入缓存
     *
     * @param age
     * @return
     */
    @CachePut(cacheNames = "t4", key = "#age")
    public String cachePut(int age) {
        return "t4:" + age + "-->" + UUID.randomUUID().toString();
    }
    

    3. @CacheEvict

    这个就是我们理解的删除缓存

    /**
     * 失效缓存
     *
     * @param name
     * @return
     */
    @CacheEvict(cacheNames = "say", key = "'p_'+ #name")
    public String evict(String name) {
        return "evict+" + name + "-->" + UUID.randomUUID().toString();
    }
    

    4. @Caching

    在实际的工作中,经常会遇到一个数据变动,更新多个缓存的场景,对于这个场景,可以通过@Caching来实现

    /**
     * caching实现组合,添加缓存,并失效其他的缓存
     *
     * @param age
     * @return
     */
    @Caching(cacheable = @Cacheable(cacheNames = "caching", key = "#age"), evict = @CacheEvict(cacheNames = "t4", key = "#age"))
    public String caching(int age) {
        return "caching: " + age + "-->" + UUID.randomUUID().toString();
    }
    

    上面这个就是组合操作

    • caching::age缓存取数据,不存在时执行方法并写入缓存;
    • 失效缓存 t4::age

    5. 异常时,缓存会怎样?

    上面的几个case,都是正常的场景,当方法抛出异常时,这个缓存表现会怎样?

    /**
     * 用于测试异常时,是否会写入缓存
     *
     * @param age
     * @return
     */
    @Cacheable(cacheNames = "exception", key = "#age")
    @Cacheable(cacheNames = "say", key = "'p_yihuihui'")
    public int exception(int age) {
        return 10 / age;
    }
    

    根据实测结果,当age==0时,上面两个缓存都不会成功

    6. 测试用例

    接下来验证下缓存注解与上面描述的是否一致

    @RestController
    public class IndexRest {
        @Autowired
        private BasicDemo helloService;
    
        @GetMapping(path = {"", "/"})
        public String hello(String name) {
            return helloService.sayHello(name);
        }
    }
    

    上面这个主要是验证@Cacheable注解,若缓存不命中,每次返回的结果应该都不一样,然而实际访问时,会发现返回的都是相同的

    curl http://localhost:8080/?name=yihuihui
    

    失效缓存

    @GetMapping(path = "evict")
    public String evict(String name) {
        return helloService.evict(String.valueOf(name));
    }
    

    失效缓存,需要和上面的case配合起来使用

    curl http://localhost:8080/evict?name=yihuihui
    curl http://localhost:8080/?name=yihuihui
    

    剩下其他的相关测试类就比较好理解了,一并贴出对应的代码

    @GetMapping(path = "condition")
    public String t1(int age) {
        return helloService.setByCondition(age);
    }
    
    @GetMapping(path = "unless")
    public String t2(int age) {
        return helloService.setUnless(age);
    }
    
    @GetMapping(path = "exception")
    public String exception(int age) {
        try {
            return String.valueOf(helloService.exception(age));
        } catch (Exception e) {
            return e.getMessage();
        }
    }
    
    @GetMapping(path = "cachePut")
    public String cachePut(int age) {
        return helloService.cachePut(age);
    }
    

    7. 小结

    最后管理小结一下Spring提供的几个缓存注解

    • @Cacheable: 缓存存在,则从缓存取;否则执行方法,并将返回结果写入缓存
    • @CacheEvit: 失效缓存
    • @CachePut: 更新缓存
    • @Caching: 都注解组合

    上面虽说可以满足常见的缓存使用场景,但是有一个非常重要的点没有说明,缓存失效时间应该怎么设置???

    如何给每个缓存设置不同的缓存失效时间,咱么下篇博文见,我是一灰灰,欢迎关注长草的公众号一灰灰blog

    III. 不能错过的源码和相关知识点

    0. 项目

    1. 一灰灰Blog

    尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

    下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

    一灰灰blog

  • 相关阅读:
    摊还分析
    web端手机方向传感器闲谈
    研一一年论文总结(下)
    Jupyter自定义设置详解
    HAProxy实现动静分离和负载均衡
    欧拉项目 323题
    mysql基本操作
    以后的IT路还很长(1)
    【翻译】在Ext JS集成第三方库
    吴裕雄--天生自然JAVA SPRING框架开发学习笔记:Spring IoC容器BeanFactory和ApplicationContext
  • 原文地址:https://www.cnblogs.com/yihuihui/p/14964648.html
Copyright © 2020-2023  润新知