springboot 2. Getting started with X - springboot2 X annotation based cache implementation and increase expiration time

In the evening, I went to a Honda car club, the civic of Yishui. Although I'm not the owner of Honda, I like cars very much, so I would go to join in the fun. The civic red in the hatchback is really handsome. Hahaha, I didn't get home until nearly ten o'clock in the evening. It's twelve o'clock after writing this article. Well, no more nonsense. Let's get to the point.     

Today is spring boot 2 X integrates the second section of redis and implements caching based on annotations. In fact, spring's CacheManager supports a variety of caching components, such as ehcache, jcache and redis, but redis is the most popular, so we take it as an example. In addition, because it supports a variety of caching databases and the ttl mechanism of each database is different, the annotations provided by spring do not have ttl setting parameters, It is very inconvenient in practical applications, so today we will add the ttl configuration of annotations by copying the CacheManager.

spring has implemented its own cache annotations based on JSR107 specification since 3.1, including Cacheable, CachePut and CacheEvict.

First, to start the cache manager, you need to add the @ EnableCaching annotation on the startup class

@EnableCaching
@SpringBootApplication
public class SpringbootRedisApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringbootRedisApplication.class, args);
	}

}

@Cacheable
This annotation can be used on a class or method. On a class, it means that all method return objects are added to the cache. If on a method, it specifies that the method return objects are added to the cache.

@CachePut
This note is related to @Cacheable Similar, except that it is executed every time and replaces the same name in the cache, which is generally used to update the cache.

@CacheEvict
This annotation is used to clear the cache. Its properties are the same as @Cacheable Compared with two more, allEntries defaults to false. The specified cache is cleared according to the key. If true, it means that the key attribute is ignored and all caches are cleared. When the beforeInvocation property is true, it means that the cache is cleared before the method is executed.

After the explanation, we enter the practical operation stage. Today we only talk about @ Cacheable. If we can use this, we can use the other two.

@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;
}

The above are the parameters of @ Cacheable annotation. Commonly used are key, value, condition and unless. These four keys are the redis key value. Value is the cache namespace, condition is the precondition and unless is the postcondition to filter data. In the annotated parameters, the SpEl expression can be used for dynamic splicing. Below, I provide some test code to demonstrate to you.

@Service
public class TestServiceImpl implements TestService{
    
    //Conventional posture
    @Override
    @Cacheable(value = {"test"}, key = "#id")
    public String test(Integer id) {
        return "test";
    }

    //With string splicing
    @Override
    @Cacheable(value = {"test1"}, key = "'id-' + #id")
    public String test1(Integer id) {
        return "test";
    }

    //Cache only those with ID > 1
    @Override
    @Cacheable(value = {"test2"}, key = "'hash' + #id",condition = "#id > 1")
    public String test2(Integer id) {
        return "test";
    }

    //null results are not cached
    @Override
    @Cacheable(value = {"test3"}, key = "#id", unless = "#result != null")
    public String test3(Integer id) {
        return null;
    }
}

The above is how to use Cacheable. It's not very convenient, but you can't set the cache expiration time. Next, we'll implement CacheManager. Next, we need to modify the RedisConfig class in the previous section and add the code!!!!!!

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // The key is serialized by String
        template.setKeySerializer(stringRedisSerializer);
        // The key of hash is also serialized by String
        template.setHashKeySerializer(stringRedisSerializer);
        // value is serialized by jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // The value serialization method of hash is jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        return new RedisCacheManager(
                RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
                this.getRedisCacheConfigurationWithTtl(30 * 60), // The default policy is used by unconfigured key s
                this.getRedisCacheConfigurationMap() // Specify key policy
        );
    }

    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
        //Configure expiration time for SsoCache and BasicDataCache
        redisCacheConfigurationMap.put("test", this.getRedisCacheConfigurationWithTtl(24 * 60 * 60));
        return redisCacheConfigurationMap;
    }

    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
                RedisSerializationContext
                        .SerializationPair
                        .fromSerializer(jackson2JsonRedisSerializer)
        ).entryTtl(Duration.ofSeconds(seconds));

        return redisCacheConfiguration;
    }

    @Bean
    public KeyGenerator wiselyKeyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append("." + method.getName());
                if (params == null || params.length == 0 || params[0] == null) {
                    return null;
                }
                String join = String.join("&", Arrays.stream(params).map(Object::toString).collect(Collectors.toList()));
                String format = String.format("%s{%s}", sb.toString(), join);
                return format;
            }
        };
    }

}

In the above code, we have configured the cache time in the CacheManager and processed the unified expiration time of the cache with a unified namespace. In use, we only need to configure the value in the annotation in the RedisConfig class to complete the configuration of the expiration time.

Welcome to my official account: codesls, let's go together hi hi Pipi.

Keywords: Database Redis Cache

Added by Nick~C on Wed, 05 Jan 2022 01:08:05 +0200