Realize the synchronization between redis cache and database

Keywords: Spring Redis Programming

I.
Through annotation, spring 3 above provides annotation for cache programming.
@Cacheable: used when querying. Note that Long type needs to be converted to Sting type, otherwise exceptions will be thrown.

@CachePut: used when updating. With this annotation, data will be queried from the DB.

@CacheEvict: used when deleting;

@Caching: please refer to the official website for the usage of specific annotation of combination usage

Note: Although the annotation method can make our code concise, it has limitations: key acquisition and invalid annotation when nesting.
// Use getUserByName to call getIdByName and getUser methods to implement queries, but use this method to call directly in the controller

//getUserByName method, the cache effect does not work. You must directly call getIdByName and getUser methods to work.

public User getUserByName(String name) {
    //Query the primary key by name and then query the entity by the primary key
    return getUser(Long.valueOf(getIdByName(name)));
}

Two.
Non annotation implementation

1. Define a RedisCacheConfig class to generate RedisTemplate and manage CacheManager

@Configuration
public class RedisCacheConfig  extends CachingConfigurerSupport {

    /*Beans defining cache data key generation strategy
     *Package name + class name + method name + all parameters
    */
    @Bean
    public KeyGenerator keyGenerator() {
        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());
                for (Object obj : params) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }

     //@Bean
     public CacheManager cacheManager(
             @SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {
         //RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
           //cacheManager.setDefaultExpiration(60); / / set cache retention time (seconds)
         return cacheManager;
     }

    //1. When the project starts, this method is first registered as a bean and managed by spring.
    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {

        StringRedisTemplate template = new StringRedisTemplate(factory);
        Jackson2JsonRedisSerializer 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);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        //Use Jackson 2jsonredisserializer to serialize and deserialize the value of redis
        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);

        System.out.println("==============obj:"+Object.class.getName());
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);

        template.setValueSerializer(serializer);
        //Use StringRedisSerializer to serialize and deserialize the key value of redis
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}

2. Define a redisUtil class to access the cache value

@Component
public class RedisCacheUtil {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * Store string
     * @param key string Type key
     * @param value String value of type
     */
    public void set(String key, String value) {
        stringRedisTemplate.opsForValue().set(key, value);
    }

    /**
     * Storage object
     * @param key String Type key
     * @param value Object value of type
     */
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * Storage object
     * @param key String Type key
     * @param value Object value of type
     */
    public void set(String key, Object value,Long timeOut) {
        redisTemplate.opsForValue().set(key, value,timeOut, TimeUnit.SECONDS);
    }

    /**
     * Get string data according to key
     * @param key
     * @return
     */
    public String getValue(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

//    public Object getValue(String key) {
//        return redisTemplate.opsForValue().get(key);
//    }
    /**
     * Get object according to key
     * @param key
     * @return
     */
    public Object getValueOfObject(String key) {
        return redisTemplate.opsForValue().get(key);
    }
    /**
     * Delete cache information according to key
     * @param key
     */
    public void delete(String key) {
        redisTemplate.delete(key);
    }
    /**
     * Query whether the key exists
     * @param key
     * @return
     */
    @SuppressWarnings("unchecked")
    public boolean exists(String key) {
        return redisTemplate.hasKey(key);
    }
}

3. Implementation class`/**
*
*

  • Based on Impl + prevent cache avalanche and cache penetration
    */
    @Service(value = "userServiceImpl4")
    public class UserServiceImpl4 implements UserService {

    @Autowired
    UserMapper userMapper;

    @Autowired
    RedisCacheUtil redisCacheUtil;

    @Value("${timeOut}")
    private long timeOut;

    @Override
    public User getUser(Long userId) {

     String key = "user" + userId;
     User user = (User) redisCacheUtil.getValueOfObject(key);
     String keySign = key + "_sign";
     String valueSign = redisCacheUtil.getValue(keySign);
     if(user == null){//Prevent spatiotemporal results from being returned on the first query
         //Prevent cache penetration
         if(redisCacheUtil.exists(key)){
             return  null;
         }
         user = userMapper.selectByPrimaryKey(userId);
    
         redisCacheUtil.set(key,user);
         redisCacheUtil.set(keySign,"1",timeOut *(new Random().nextInt(10) + 1));
    

//redisCacheUtil.set(keySign, "1", 0L); / / expiration time cannot be set to 0, it must be a number greater than 0
return user;
}

    if(valueSign != null){
        return user;
    }else {
        //Set effective time of tag
        Long tt = timeOut * (new Random().nextInt(10) + 1);
        System.out.println("tt:"+tt);
        redisCacheUtil.set(keySign,"1",tt);
        //Asynchronous processing of cache update response and high concurrency will result in dirty reads
        ThreadPoolUtil.getExecutorService().execute(new Runnable(){
            public void run() { //
                System.out.println("-----Perform asynchronous operation-----");
                User user1 = userMapper.selectByPrimaryKey(userId);
                redisCacheUtil.set(key,user1);
            }
        });

// new Thread(){
//public void run() {/ / in case of high concurrency, dirty reading will occur.
//System.out.println("------ perform asynchronous operation -----");
// User user1 = userMapper.selectByPrimaryKey(userId);
// redisCacheUtil.set(key,user1);
// }
// }.start();
}
return user;
}
}`

Posted by JeroenVO on Tue, 15 Oct 2019 09:08:27 -0700