Set cache condition action using Spring Cache

  • 2021-11-13 07:27:09
  • OfStack

Directory Spring Cache Setting Cache Condition Principle @ Cacheable Common Attributes and Description Root Object @ CachePut Common Attributes Same as @ CacheableCache Cache Configuration 1, pom. xml2, Ehcache Configuration File 3, Configuration Class 4, Example

Spring Cache Setting Cache Conditions

Principle

Starting from Spring3.1, Spring framework provides support for Cache and provides an abstraction of caching. By adding a small number of annotation defined by it to existing code, it can achieve the function of returning objects of caching methods.

The main annotations provided are @ Cacheable, @ CachePut, @ CacheEvict, and @ Caching, as shown in the following table:

注解 说明
@Cacheable 可以标注在类或方法上:标注在方法上表示该方法支持数据缓存;标在类上表示该类的所有方法都支持数据缓存。 具体功能:在执行方法体之前,检查缓存中是否有相同key值的缓存存在,如果存在对应的缓存,直接返回缓存中的值;如果不存在对应的缓存,则执行相应的方法体获取数据,并将数据存储到缓存中。
@CachePut 可以标注在类或方法上,表示支持数据缓存。 具体功能:在方法执行前不会检查缓存中是否存在相应的缓存,而是每次都会执行方法体,并将方法执行结果存储到缓存中,如果相应key值的缓存存在,则更新key对应的value值。
@CacheEvict 可以标注在类或方法上,用于清除相应key值的缓存。
@Caching 可以标注在类或方法上,它有3个属性cacheable、put、evict分别用于指定@Cacheable、@CachePut和@CacheEvict

When you need to use multiple annotations on a class or method at the same time, you can use @ Caching, such as:


@Caching(cacheable=@Cacheable("User"), evict = {@CacheEvict("Member"), @CacheEvict(value = "Customer", allEntries = true)})

Common attributes and descriptions of @ Cacheable

As shown in the following table:

@Cacheable属性 说明
key 表示缓存的名称,必须指定且至少要有1个值,比如:@Cacheable(value=“Dept”)或@Cacheable(value={“Dept”,“Depts”})
condition 表示是否需要缓存,默认为空,表示所有情况都会缓存。通过SpEL表达式来指定,若condition的值为true则会缓存,若为false则不会缓存,如@Cacheable(value=“Dept”,key="‘deptno_'+# deptno “,condition=”#deptno<=40")
value 表示缓存的key,支持SpEL表达式,如@Cacheable(value=“Dept”,key="‘deptno_' +#deptno"),可以不指定值,如果不指定,则缺省按照方法的所有参数进行组合。除了上述使用方法参数作为key之外,Spring还提供了1个root对象用来生成key,使用方法如下表所示,其中"#root"可以省略。

Root Object

Root对象 说明
methodName 当前方法名,比如#root.methodName
method 当前方法,比如#root.method.name
target 当前被调用的对象,比如#root.target
targetClass 当前被调用的对象的class,比如#root.targetClass
args 当前方法参数组成的数组,比如#root.args[0]
caches 当前被调用的方法使用的缓存,比如#root.caches[0].name

Common attributes of @ CachePut are the same as @ Cacheable

The common properties of @ CacheEvict are shown in the following table:

@CacheEvict属性 说明
value 表示要清除的缓存名
key 表示需要清除的缓存key值,
condition 当condition的值为true时才清除缓存
allEntries 表示是否需要清除缓存中的所有元素。默认为false,表示不需要,当指定了allEntries为true时,将忽略指定的key。
beforeInvocation 清除操作默认是在方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当该属性值为true时,会在调用该方法之前清除缓存中的指定元素。

Example: Setting to cache when the length of dname is greater than 3


// Conditional cache 
@ResponseBody
@GetMapping("/getLocByDname")
@Cacheable(cacheNames = "dept", key = "#dname", condition = "#dname.length()>3")
public String getLocByDname(@RequestParam("dname") String dname) {//key Dynamic parameter 
    QueryWrapper<Dept> queryMapper = new QueryWrapper<>();
    queryMapper.eq("dname", dname);
    Dept dept = deptService.getOne(queryMapper);
    return dept.getLoc();
}

Example: unless is caching when the condition is not true

# result stands for the return value, meaning it is not cached when the return code is not equal to 200, that is, it is cached only when it is equal to 200.


@ResponseBody
@GetMapping("/getDeptByDname")
@Cacheable(cacheNames = "dept", key = "#dname", unless = "#result.code != 200")
public Result<Dept> getDeptByDname(@RequestParam("dname") String dname){//key Dynamic parameter 
    QueryWrapper<Dept> queryMapper = new QueryWrapper<>();
    queryMapper.eq("dname", dname);
    Dept dept = deptService.getOne(queryMapper);
    if (dept == null)
        return ResultUtil.error(120, "dept is null");
    else
        return ResultUtil.success(dept);
}

Cache Cache Configuration

1. pom. xml


<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--  The reflection tool class is used to manually scan the annotations under the specified package, according to the defaultCache Module addition ehcache Cache domain (non- Spring Cache Must) -->
<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.11</version>
</dependency>

2. Ehcache configuration file


<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--  Disk cache location  -->
    <diskStore path="java.io.tmpdir" />
    <!--
        name: Cache name. 
        maxElementsInMemory Maximum number of caches. 
        eternal: Object is permanently valid, 1 But it was set, timeout Will not work. 
        timeToIdleSeconds Sets the allowed idle time (in seconds) before the object expires. Only if eternal=false Object is not permanently valid, optional property, and the default value is 0 That is, the idle time is infinite. 
        timeToLiveSeconds Sets how long an object is allowed to live before it expires (in seconds). The maximum time is between the creation time and the expiration time. Only if eternal=false Object is not permanently valid, and the default is 0. That is, the object lives for infinity. 
        overflowToDisk When the number of objects in memory reaches maxElementsInMemory When, Ehcache Writes the object to disk. 
        diskSpoolBufferSizeMB This parameter sets DiskStore The size of the cache (disk cache). The default is 30MB . Each Cache Everyone should have their own 1 Buffers. 
        maxElementsOnDisk Maximum number of hard disk caches. 
        diskPersistent Whether to cache virtual machine restart period data  Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
        diskExpiryThreadIntervalSeconds Disk failure thread elapsed time interval, the default is 120 Seconds. 
        memoryStoreEvictionPolicy : When reached maxElementsInMemory When limiting, Ehcache The memory will be cleaned according to the specified policy. The default policy is LRU (Least recently used). You can set it to FIFO (First in, first out) or LFU (Less used). 
        clearOnFlush Whether to clear the maximum amount of memory. 
    -->
    <!--  Default cache  -->
    <defaultCache
            eternal="false"
            maxElementsInMemory="200000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="0"
            timeToLiveSeconds="600"
            memoryStoreEvictionPolicy="LRU" />
</ehcache>

3. Configure classes


@Configuration
@EnableCaching
public class CustomConfiguration {
    /**
     * @see org.springframework.cache.interceptor.SimpleKeyGenerator
     * Generate a key based on the specified parameters.
     */
    public static Object generateKey(Object... params) {
        if (params.length == 0) {
            return SimpleKey.EMPTY;
        }
        if (params.length == 1) {
            Object param = params[0];
            if (param != null && !param.getClass().isArray()) {
                return param;
            }
        }
        return new SimpleKey(params);
    }
/**
 *  If the target As key Adj. 1 Part of the time, CGLIB Dynamic proxy may lead to duplicate caching 
 *  Note: Returned key1 Be sure to rewrite hashCode() And toString() , prevent key Object does not 1 The resulting cache cannot be hit 
 *  For example: ehcache  Bottom layer storage net.sf.ehcache.store.chm.SelectableConcurrentHashMap#containsKey
 */
    @Bean
    public KeyGenerator customKeyGenerator(){
        return (target, method, params) -> {
            final Object key = generateKey(params);
            StringBuffer buffer = new StringBuffer();
            buffer.append(method.getName());
            buffer.append("::");
            buffer.append(key.toString());
//  Attention 1 Be sure to turn String, Otherwise ehcache key Object may not 1 Sample, causing the cache to fail to hit 
            return buffer.toString();
        };
    }
    /**
     * redis Cache manager 
     */
    @Bean
    @ConditionalOnBean(RedisConfiguration.class)
    @ConditionalOnProperty(prefix = "spring.cache", name = "type", havingValue = "redis",
            matchIfMissing = false)
    public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .entryTtl(Duration.ofMinutes(10));
        return RedisCacheManager
                .builder(RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory))
                .cacheDefaults(config).build();
    }
/**
 * ehcache Cache manager ( Default )
 * default XML files {@link net.sf.ehcache.config.ConfigurationFactory#parseConfiguration()}
 */
    @Bean
    @ConditionalOnProperty(prefix = "spring.cache", name = "type", havingValue = "ehcache",
            matchIfMissing = true)
    public CacheManager ehcacheCacheManager() {
        net.sf.ehcache.CacheManager cacheManager = net.sf.ehcache.CacheManager.create();
        /**
         *  Package scan finds the specified annotation and sets the cacheNames Add to net.sf.ehcache.CacheManager (Singleton) 
         */
        Reflections reflections = new Reflections("com.example.demo.service", new TypeAnnotationsScanner()
                , new SubTypesScanner(), new MethodAnnotationsScanner());
        Set<Class<?>> classesList = reflections.getTypesAnnotatedWith(CacheConfig.class);
        for (Class<?> aClass : classesList) {
            final CacheConfig config = AnnotationUtils.findAnnotation(aClass, CacheConfig.class);
            if (config.cacheNames() != null && config.cacheNames().length > 0) {
                for (String cacheName : config.cacheNames()) {
                    cacheManager.addCacheIfAbsent(cacheName);
                }
            }
        }
        /**
         *  Method-level annotations  @Caching , @CacheEvict , @Cacheable , @CachePut, Only scan in combination with actual business scenarios @Cacheable You can 
         */
        final Set<Method> methods = reflections.getMethodsAnnotatedWith(Cacheable.class);
        for (Method method : methods) {
            final Cacheable cacheable = AnnotationUtils.findAnnotation(method, Cacheable.class);
            if (cacheable.cacheNames() != null && cacheable.cacheNames().length > 0) {
                for (String cacheName : cacheable.cacheNames()) {
                    cacheManager.addCacheIfAbsent(cacheName);
                }
            }
        }
        EhCacheCacheManager ehCacheCacheManager = new EhCacheCacheManager();
        ehCacheCacheManager.setCacheManager(cacheManager);
        return ehCacheCacheManager;
    }
}

4. Example


@Component
@CacheConfig(cacheNames = "XXXServiceImpl", keyGenerator = "customKeyGenerator")
public class XXXServiceImpl extends ServiceImpl<XXXMapper, XXXEntity> implements XXXService {
    @CacheEvict(allEntries = true)
    public void evictAllEntries() {}
    @Override
    @Cacheable
    public List<XXXEntity> findById(Long id) {
        return this.baseMapper.selectList(new QueryWrapper<XXXEntity>().lambda()
                .eq(XXXEntity::getId, id));
    }
}

Related articles: