1

SpringBoot series cache annotation @Cacheable @CacheEvit @CachePut use posture introduction

In version 3.1, Spring provides an annotation-based caching strategy, which is still very silky in actual use. This article will briefly introduce and explain several commonly used annotations. Friends in need can try it

The main knowledge points of this article:

  • @Cacheable: If the cache exists, use the cache; if it does not exist, execute the method and stuff the result into the cache
  • @CacheEvit: Invalidate cache
  • @CachePut: Update cache

<!-- more -->

I. Project environment

1. Project dependencies

This project is developed with the help of SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA + redis5.0

Open a web service for testing

<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>

Use the default configuration throughout the whole process, redis native, port 6379, no password

II. Introduction to Cache Annotations

1. @Cacheable

This annotation is used to decorate the method or class. When we access the modified method, it is first obtained from the cache. If the cache exists, the cached value is directly obtained; when the cache does not exist, the method is executed and the result is written to the cache

This annotation has two core settings

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

    
    @AliasFor("value")
    String[] cacheNames() default {};

    /**
     * 缓存key
     */
    String key() default "";

CacheNames can be understood as the prefix of the cache key, which can be the key variable of the component cache; when the key is not set, use the method parameter to initialize, note that the key is a SpEL expression, so if you want to write a string, enclose it in single quotes

A simple use posture

/**
 * 首先从缓存中查,查到之后,直接返回缓存数据;否则执行方法,并将结果缓存
 * <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();
}

If we pass the parameter as yihuihui, then the cache key is say::p_yihuihui

In addition to the above three configuration values, check @Cacheable annotation source code, you can see that there are also condition settings, which means that when the conditions set by it are met, it will be written to the cache

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

In the above case, when the age is an even number, the cache is used; otherwise, the cache is not written

Next is the unless parameter, as can be seen from the name, it means that it is written to the cache when the conditions are not met.

/**
 * 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

Regardless of whether there is a cache, the return result of the method is written to the cache; suitable for cache updates

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

3. @CacheEvict

This is what we understand to delete the cache

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

4. @Caching

In actual work, you often encounter a data change and update multiple cache scenarios. For this scenario, you can use @Caching to achieve

/**
 * 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();
}

The above is the combined operation

  • caching::age data from the 060df14e9bd8e1 cache, execute the method when it does not exist, and write it into the cache;
  • Invalidation cache t4::age

5. What happens to the cache when an exception occurs?

The above cases are all normal scenarios. When the method throws an exception, what will the cache behave like?

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

According to the measured results, when age==0 , the above two caches will not succeed

6. Test cases

Next, verify that the cache annotations are consistent with those described above

@RestController
public class IndexRest {
    @Autowired
    private BasicDemo helloService;

    @GetMapping(path = {"", "/"})
    public String hello(String name) {
        return helloService.sayHello(name);
    }
}

The above is mainly to verify the @Cacheable annotation. If the cache is not hit, the result returned should be different each time, but in actual access, you will find that the returned result is the same

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

invalidation cache

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

Invalidation cache, need to be used in conjunction with the above case

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

The rest of the other related test classes are easier to understand, and the corresponding code will be posted together

@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. Summary

Finally, the management summary is a few cache annotations provided by Spring

  • @Cacheable : If the cache exists, fetch it from the cache; otherwise, execute the method and write the returned result into the cache
  • @CacheEvit : invalidation cache
  • @CachePut : Update cache
  • @Caching : All annotation combination

Although the above can meet common cache usage scenarios, there is a very important point that is not explained. How should the cache invalidation time be set? ? ?

How to set a different cache expiration time for each cache? See you in the next blog post. I'm a gray person, welcome to pay attention to 一灰灰blog

III. Source code and related knowledge points not to be missed

0. Project

1. A Grey Blog

It is not as good as the letter. The above content is purely a family statement. Due to limited personal ability, it is inevitable that there will be omissions and errors. If you find a bug or have better suggestions, criticisms and corrections are welcome, and I am grateful.

The following is a gray personal blog, which records all the blog posts in study and work. Welcome everyone to visit

一灰灰blog


小灰灰Blog
251 声望46 粉丝