Home >Java >javaTutorial >How SpringBoot uses @Cacheable for caching and value retrieval

How SpringBoot uses @Cacheable for caching and value retrieval

王林
王林forward
2023-05-20 13:30:171988browse

Use @Cacheable for caching and value acquisition

1. The role of @Cacheable

The step to use caching is to implement caching by using the annotation @Cacheable. So we can first talk about the steps to use the cache:

Use the @EnableCaching annotation on the main startup class of SpringBoot to enable annotation-based caching.

Just mark the cache annotation

Step 1: Enable annotation-based caching and use @EnableCaching to mark the springboot main startup class

//开启基于注解的缓存
@EnableCaching   
@EnableRyFeignClients
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class ZfjgAuthApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZfjgAuthApplication.class, args);
    }
}

Step 2: Mark the cache annotation

@Repository
public interface DeviceMapper {
    @Cacheable(cacheNames = "DeviceDO.deviceId")
    DeviceDO get(String deviceId);
    @CacheEvict(cacheNames = "DeviceDO.deviceId", key = "#record.deviceId")
    int insert(DeviceDO record);
}

Note: The @Cacheable annotation can be used here to cache the running results. In the future, the same data can be queried directly from the cache without calling methods.

2. Description of common attributes

The following is an introduction to several commonly used attributes of the @Cacheable annotation:

  • cacheNames/ value: Used to specify the name of the cache component

  • key: The key used when caching data, you can use it to specify. The default is to use the value of the method parameter. (You can use spEL expressions to write this key)

  • keyGenerator: Key generator. Use key or keyGenerator

  • cacheManager: can be used to specify the cache manager. From which cache manager to obtain the cache.

  • condition: Can be used to specify caching only when conditions are met

  • ##unless : Negate caching. When the condition specified by unless is true, the return value of the method will not be cached. Of course, you can also get the results for judgment. (Get the method result through #result)

  • sync: Whether to use asynchronous mode.

Instructions for using Cacheable in SpringBoot

Function description

When the @Cacheable annotation is used on a method, this means that the result returned by the method can is cached. That is to say, the return result of this method will be placed in the cache, so that when the method is called with the same parameters in the future, the value in the cache will be returned without actually executing the method.

Note that one point is emphasized here: the parameters are the same. This should be easy to understand, because the cache does not care about the execution logic of the method. What it can determine is: for the same method, if the parameters are the same, the return result is also the same. But if the parameters are different, the cache can only assume that the results are different, so for the same method, how many parameter combinations are used to call the method during the running of your program, theoretically how many cached keys will be generated (of course , the parameters of these combinations refer to the generated key). Let’s take a look at some parameters of @Cacheable:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
    @AliasFor("cacheNames")
    String[] value() default {};
    @AliasFor("value")
    String[] cacheNames() default {};
    String key() default "";
    String keyGenerator() default "";
    String cacheManager() default "";
    String cacheResolver() default "";
    String condition() default "";
    String unless() default "";
    boolean sync() default false;
}

1. cacheNames & value

You can choose to use one of value or cacheNames to specify the cache name, which can be achieved through @Cacheable . This is the simplest usage example of @Cacheable:

@Override
@Cacheable("menu")
public Menu findById(String id) {
    Menu menu = this.getById(id);
    if (menu != null){
        System.out.println("menu.name = " + menu.getName());
    }
    return menu;
}

In this example, the menu cache is associated with a method named findById. If there is already a result in the menu cache, it will not be executed when calling this method, but the cached result will be used directly.

2. Associate multiple cache names

In fact, according to the official documentation, @Cacheable supports the same method to associate multiple caches. In this case, before executing the method, each of these associated caches will be checked, and as long as

at least one of the caches hits, the value in this cache will be returned.

Example:

@Override
    @Cacheable({"menu", "menuById"})
    public Menu findById(String id) {
        Menu menu = this.getById(id);
        if (menu != null){
            System.out.println("menu.name = " + menu.getName());
        }
        return menu;
    }
---------
@GetMapping("/findById/{id}")
public Menu findById(@PathVariable("id")String id){
    Menu menu0 = menuService.findById("fe278df654adf23cf6687f64d1549c0a");
    Menu menu2 = menuService.findById("fb6106721f289ebf0969565fa8361c75");
    return menu0;
}

For the sake of intuition, write the id parameter directly into the code. Now, let's test it and see the result:

How SpringBoot uses @Cacheable for caching and value retrieval

3. key & keyGenerator

A cache name corresponds to an annotated method, but a method Different parameters may be passed in, and the results will be different. How should we distinguish this? This requires the use of key . In the Spring framework, key values ​​can be generated in two ways: explicitly specified and automatically using a key generator.

3.1 KeyGenerator automatically generates

When we declare @Cacheable without specifying the key parameter, all keys under the cache name will be automatically generated using KeyGenerator based on the parameters. Spring has a default SimpleKeyGenerator, which will be injected by default in the spring boot automated configuration. The generation rules are as follows:

  • If the cache method has no parameters, return SimpleKey.EMPTY;

  • If the cache method has one parameter, return the Instance of parameters;

  • If the cache method has multiple parameters, return a SimpleKey containing all parameters;

Default key generator Requires arguments to have valid implementations of hashCode() and equals() methods. Alternatively, you can customize the keyGenerator and specify it via it. I won’t give a detailed introduction to KeyGenerator here. If you are interested, you can take a look at the source code. In fact, it uses hashCode to perform addition and multiplication operations. Similar to hash calculations for String and ArrayList.

3.2 Explicitly specify key

相较于使用 KeyGenerator 生成,spring 官方更推荐显式指定 key 的方式,即指定 @Cacheable 的 key 参数。

即便是显式指定,但是 key 的值还是需要根据参数的不同来生成,那么如何实现动态拼接呢?SpEL(Spring Expression Language,Spring 表达式语言) 能做到这一点。下面是一些使用 SpEL 生成 key 的例子。

@Override
    @Cacheable(value = {"menuById"}, key = "#id")
    public Menu findById(String id) {
        Menu menu = this.getById(id);
        if (menu != null){
            System.out.println("menu.name = " + menu.getName());
        }
        return menu;
    }
    @Override
    @Cacheable(value = {"menuById"}, key = "'id-' + #menu.id")
    public Menu findById(Menu menu) {
        return menu;
    }
    @Override
    @Cacheable(value = {"menuById"}, key = "'hash' + #menu.hashCode()")
    public Menu findByHash(Menu menu) {
        return menu;
    }

How SpringBoot uses @Cacheable for caching and value retrieval

官方说 key 和 keyGenerator 参数是互斥的,同时指定两个会导致异常。

4. cacheManager & cacheResolver

缓存管理器CacheManager用于管理(寻找)某种缓存。通常来讲,缓存管理器是与缓存组件类型相关联的。我们知道,spring 缓存抽象的目的是为使用不同缓存组件类型提供统一的访问接口,以向开发者屏蔽各种缓存组件的差异性。那么 CacheManager 就是承担了这种屏蔽的功能。spring 为其支持的每一种缓存的组件类型提供了一个默认的 manager,如:RedisCacheManager 管理 redis 相关的缓存的检索、EhCacheManager 管理 ehCache 相关的缓等。

CacheResolver,缓存解析器是用来管理缓存管理器的,CacheResolver 保持一个 cacheManager 的引用,并通过它来检索缓存。CacheResolver 与 CacheManager 的关系有点类似于 KeyGenerator 跟 key。spring 默认提供了一个 SimpleCacheResolver,开发者可以自定义并通过 @Bean 来注入自定义的解析器,以实现更灵活的检索。

大多数情况下,我们的系统只会配置一种缓存,所以我们并不需要显式指定 cacheManager 或者 cacheResolver。Spring允许我们在系统中配置多个缓存组件,这时我们需要进行明确的指定。指定的方式是使用 @Cacheable 的 cacheManager 或者 cacheResolver 参数。

按照官方文档,cacheManager 和 cacheResolver 是互斥参数,同时指定两个可能会导致异常。

5. sync

是否同步,true/false。在一个多线程的环境中,某些操作可能被相同的参数并发地调用,这样同一个 value 值可能被多次计算(或多次访问 db),这样就达不到缓存的目的。针对这些可能高并发的操作,我们可以使用 sync 参数来告诉底层的缓存提供者将缓存的入口锁住,这样就只能有一个线程计算操作的结果值,而其它线程需要等待,这样就避免了 n-1 次数据库访问。

sync = true 可以有效的避免缓存击穿的问题。

6. condition

调用前判断,缓存的条件。有时候,我们可能并不想对一个方法的所有调用情况进行缓存,我们可能想要根据调用方法时候的某些参数值,来确定是否需要将结果进行缓存或者从缓存中取结果。例如,当我查询用户时按年龄分组,我只需缓存那些年龄大于 35 的结果。那么 condition 能实现这种效果。

SpEL 支持的表达式可以作为 condition 的值,结果为 true 或 false。如果表达式结果为 true,则调用方法时会执行正常的缓存逻辑(查缓存-有就返回-没有就执行方法-方法返回不空就添加缓存);否则,调用方法时就好像该方法没有声明缓存一样(即无论传入了什么参数或者缓存中有些什么值,都会执行方法,并且结果不放入缓存)。下面举个例子:

我们看一下数据库,以这两条数据为例:

  How SpringBoot uses @Cacheable for caching and value retrieval

我们首先定义一个带条件的缓存方法:

@Override
    @Cacheable(value = {"menuById"}, key = "#id", condition = "#conditionValue > 1")
    public Menu findById(String id, Integer conditionValue) {
        Menu menu = this.getById(id);
        if (menu != null){
            System.out.println("menu.name = " + menu.getName());
        }
        return menu;
    }

然后分两种情况调用(为了直观可见,直接将 id 写在代码中):

@GetMapping("/findById/{id}")
    public Menu findById(@PathVariable("id")String id){
        Menu menu0 = menuService.findById("fe278df654adf23cf6687f64d1549c0a", 0);
        Menu menu2 = menuService.findById("fb6106721f289ebf0969565fa8361c75", 2);
        return menu0;
    }

How SpringBoot uses @Cacheable for caching and value retrieval

How SpringBoot uses @Cacheable for caching and value retrieval

虽然两次请求都调用了方法,但只有第二次请求缓存了"微服务测试2"。只有在满足条件 condition 的情况下才会进行调用,这样才能将结果缓存。接下来我们再请求一遍,看下结果和打印:

How SpringBoot uses @Cacheable for caching and value retrieval

How SpringBoot uses @Cacheable for caching and value retrieval

可以看到,“微服务测试2”由于已经有了缓存,所以没有再执行方法体。而“微服务测试0”又一次执行了。

7. unless

执行后判断,不缓存的条件。SpEL 可以支持表达式结果为 true 或 false,用于 unless。当结果为 true 时,不缓存。举个例子:

我们先清除 redis 中的数据。然后看看 mysql 中的数据:

  How SpringBoot uses @Cacheable for caching and value retrieval

然后编写一个缓存方法(在该方法中,result代表方法的返回值。关于):

@Override
    @Cacheable(value = {"menuById"}, key = "#id", unless = "#result.type == 'folder'")
    public Menu findById(String id) {
        Menu menu = this.getById(id);
        if (menu != null){
            System.out.println("menu.name = " + menu.getName());
        }
        return menu;
    }

然后调用该方法:

@GetMapping("/findById/{id}")
    public Menu findById(@PathVariable("id")String id){
        Menu menu0 = menuService.findById("fe278df654adf23cf6687f64d1549c0a");
        Menu menu2 = menuService.findById("fb6106721f289ebf0969565fa8361c75");
        return menu0;
    }

How SpringBoot uses @Cacheable for caching and value retrieval

How SpringBoot uses @Cacheable for caching and value retrieval

可以看到,两次都执行了方法体(其实,unless 条件就是在方法执行完毕后调用,所以它不会影响方法的执行),但是结果只有 menu.type = ‘page’ 的缓存了,说明 unless 参数生效了。

8. condition VS unless ?

既然 condition 和 unless 都能决定是否进行缓存,那么同时指定这两个参数并且结果相冲突的时候,会怎么样呢?我们来试一试。

首先清除 redis 数据,然后在缓存方法上加上 condition=“true”,如:

@Override
    @Cacheable(value = {"menuById"}, key = "#id", condition = "true", unless = "#result.type == 'folder'")
    public Menu findById(String id) {
        Menu menu = this.getById(id);
        if (menu != null){
            System.out.println("menu.name = " + menu.getName());
        }
        return menu;
    }

其它代码不变,我们来看一下缓存结果和打印:

How SpringBoot uses @Cacheable for caching and value retrieval

How SpringBoot uses @Cacheable for caching and value retrieval

可以看到,虽然两次调用都执行了,但是,type=‘folder’ 的还是被排除了。在这种情况下,unless 的优先级高于 condition。接着,我们将condition设为“false”,再进行尝试,结果是:

How SpringBoot uses @Cacheable for caching and value retrieval

How SpringBoot uses @Cacheable for caching and value retrieval

可以看到,两次调用的结果都没有缓存。在这种情况下,优先使用condition而不是unless。总结起来就是:

  • condition 不指定相当于 true,unless 不指定相当于 false

  • condition = false,一定不会缓存;

  • condition = true,且 unless = true,不缓存;

  • condition = true,且 unless = false,缓存;

The above is the detailed content of How SpringBoot uses @Cacheable for caching and value retrieval. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:yisu.com. If there is any infringement, please contact admin@php.cn delete