@Cacheable using spring cache

First, first understand the annotations that come with spring

  • First, you need to enable this function in the startup class
package com.frame.util;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
@MapperScan("com.frame.util.dao")
public class UtilApplication {
    
    

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

}

Springboot's own annotations mainly include @Cacheable, @CacheEvict and @CachePut, mainly implemented by AOP proxy

1.@Cacheable

  1. @Cacheable can be marked on a class or method. When it is marked on a method, it means that the method supports caching. When it is marked on a class, it means that all methods of the class support caching. For the method with this annotation, spring will first determine whether there is a cache before the same parameter of the method, and if so, it will return directly without going through the logic of the method; if not, the method needs to be re-executed, and the method will be executed. The result is put into the cache. Consider the following example:
package com.frame.util.service.redis;

import com.frame.util.dao.TbPhoneDao;
import com.frame.util.entity.dataObject.TbPhone;
import com.frame.util.entity.vo.ResultVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @Author: towards
 * @CreateTime: 2022-07-16  11:33
 * @Description: 缓存注解
 * @Version: 1.0
 */
@Slf4j
@Service
public class CacheAnnotionServiceimpl {
    
    

    private final String phoneKey = "PHONE_";

    @Resource
    private TbPhoneDao tbPhoneDao;

    @Autowired
    private RedisService redisService;

    /**
     * @description: 通过手机名获取手机
     * @author: towards
     * @date: 2022/7/16 11:37
     * @param: name
     * @return: com.frame.util.vo.ResultVo
     **/

    @Cacheable(cacheNames = "phoneCache",key = "#id")
    public <T> ResultVo getPhone(Integer id) throws Exception {
    
    
        T value = redisService.getKey(keyGenorate(id));
        if (ObjectUtils.isNotEmpty(value)){
    
    
            // 如果缓存中有,直接返回
            log.info("query phone from redis");
            return ResultVo.success(value);
        }
        // 缓存中没有,查询数据不为空时,存入缓存
        TbPhone tbPhone = tbPhoneDao.queryById(id);
        if (!ObjectUtils.isEmpty(tbPhone)){
    
    
            log.info("query from mysql ,and save to redis");
            redisService.setKey(keyGenorate(id), tbPhone);
            return ResultVo.success(tbPhone);
        }
        // 缓存和数据库都没有
        throw new Exception("query phone in redis and mysql is not exist");
    }

    /**
     * @description: 根据id生成redis的key
     * @author: towards
     * @date: 2022/7/16 12:12
     * @param: id
     * @return: java.lang.String
     **/
    public String keyGenorate(Integer id){
    
    
        String redisKey = phoneKey+id;
        return redisKey;
    }


}

  • When the getPhone method is accessed for the first time with id=1, the getPhone method will execute the internal logic and put the result into the cache named phoneCache. The next time the method is accessed with id=1, the internal logic of the method will not be executed.
  • There are some properties that need to be explained to facilitate use in different scenarios:
  • 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. (This key you can use spEL expression to write)
  • keyGenerator : The generator of the key. Use key and keyGenerator alternatively
  • cacheManager : Can be used to specify a cache manager. Which cache manager to get the cache from.
  • condition : can be used to specify that the cache will only be cached if the condition is met
  • unless : Negates caching. When the condition specified by unless is true, the return value of the method is not cached. Of course, you can also get the results to judge. (Get the method result through #result)
  • sync : Whether to use asynchronous mode.
  • Custom KeyGenerator
package com.frame.util.config;

import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * @Author: towards
 * @CreateTime: 2022-07-16  16:45
 * @Description: TODO
 * @Version: 1.0
 */
@Configuration
public class KeyGenerateConfig {
    
    

    @Bean("myKeyGenerate")
    public KeyGenerator keyGenerator(){
    
    
        return new KeyGenerator() {
    
    
            @Override
            public Object generate(Object target, Method method, Object... params) {
    
    
                return method.getName()+"["+ Arrays.asList(params).toString()+"]";
            }
        };
    }
}

  • Use keyGenerate as cache key
@Cacheable(cacheNames = "phoneCache",keyGenerator = "myKeyGenerate")
    public <T> ResultVo getPhone(Integer id) throws Exception {
    
    
        T value = redisService.getKey(keyGenorate(id));
        if (ObjectUtils.isNotEmpty(value)){
    
    
            // 如果缓存中有,直接返回
            log.info("query phone from redis");
            return ResultVo.success(value);
        }
        // 缓存中没有,查询数据不为空时,存入缓存
        TbPhone tbPhone = tbPhoneDao.queryById(id);
        if (!ObjectUtils.isEmpty(tbPhone)){
    
    
            log.info("query from mysql ,and save to redis");
            redisService.setKey(keyGenorate(id), tbPhone);
            return ResultVo.success(tbPhone);
        }
        // 缓存和数据库都没有
        throw new Exception("query phone in redis and mysql is not exist");
    }

2.@CachePut

  • @CachePut can also declare a method to support caching functionality. The difference from @Cacheable is that the method annotated with @CachePut will not check whether there is a previously executed result in the cache before execution, but will execute the method every time, and store the execution result in the form of a key-value pair. in the specified cache.
  • The attribute values ​​such as key and value are the same as @Cacheable, and the method of obtaining the key is also the same
  • Generally used in updated scenarios, the cache needs to be updated. The following describes the code implementation:
@CachePut(cacheNames = "phoneCache" , keyGenerator = "myKeyGenerate")
    public <T> ResultVo getPhone(Integer id) throws Exception {
    
    
        T value = redisService.getKey(keyGenorate(id));
        if (ObjectUtils.isNotEmpty(value)){
    
    
            // 如果缓存中有,直接返回
            log.info("query phone from redis");
            return ResultVo.success(value);
        }
        // 缓存中没有,查询数据不为空时,存入缓存
        TbPhone tbPhone = tbPhoneDao.queryById(id);
        if (!ObjectUtils.isEmpty(tbPhone)){
    
    
            log.info("query from mysql ,and save to redis");
            redisService.setKey(keyGenorate(id), tbPhone);
            return ResultVo.success(tbPhone);
        }
        // 缓存和数据库都没有
        throw new Exception("query phone in redis and mysql is not exist");
    }
  • It will be found that the method logic will be executed every time, regardless of whether the cache exists or not.

3.@CacheEvict

  • allEntries is a boolean type indicating whether to clear all elements in the cache. The default is false, which means it is not required. When allEntries is specified as true, Spring Cache will ignore the specified key. Sometimes we need to clear all elements in Cache at once, which is more efficient than clearing elements one by one.
  • By default, the clearing operation is triggered after the corresponding method is successfully executed, that is, if the method fails to return successfully because an exception is thrown, the clearing operation will not be triggered. Use beforeInvocation to change the time when the clear operation is triggered. When we specify the value of this property to be true, Spring will clear the specified element in the cache before calling this method.

4.@Caching

  • The @Caching annotation allows us to specify multiple Spring Cache-related annotations on a method or class at the same time. It has three properties: cacheable, put and evict, used to specify @Cacheable, @CachePut and @CacheEvict respectively.
   @Caching(cacheable = @Cacheable("users"), evict = {
    
     @CacheEvict("cache2"),
         @CacheEvict(value = "cache3", allEntries = true) })
   public User find(Integer id) {
    
    
      returnnull;
 
   }

5.@CacheConfig

  • Used to extract the common configuration below this class, the following is the source code
package org.springframework.cache.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {
    
    
    String[] cacheNames() default {
    
    };

    String keyGenerator() default "";

    String cacheManager() default "";

    String cacheResolver() default "";
}
  • Can be marked on the class, like this
    insert image description here

2. The above uses Spring's own cache. You can modify the cache configuration and change it to redis. The following describes the process

  1. configure
package com.frame.util.config;

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.beans.factory.annotation.Autowired;
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.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;

import java.time.Duration;

/**
 * @Description:
 * @date: 2022/5/25 15:21
 * @author: towards
 * @since JDK 1.8
 */

/**
 * 自定义 RedisTemplate
 */
@Configuration
public class RedisConfig {
    
    

    @Autowired
    private LettuceConnectionFactory lettuceConnectionFactory;

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    
    
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer<Object> 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.WRAPPER_ARRAY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // key 采用 String 的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash 的 key 也采用 String 的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value 序列化方式采用 jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash 的 value 序列化方式采用 jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();

        return template;
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate){
    
    
        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMillis(30000))//设置缓存过期时间
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
        RedisCacheManager build = RedisCacheManager.builder(lettuceConnectionFactory).cacheDefaults(configuration)
                .build();
        return build;
    }


}

  1. use annotations
    @Cacheable(cacheNames = "phoneCache",cacheManager = "redisCacheManager",keyGenerator = "myKeyGenerate")
public <T> ResultVo getPhone(Integer id) throws Exception {
    
    
        T value = redisService.getKey(keyGenorate(id));
        if (ObjectUtils.isNotEmpty(value)){
    
    
            // 如果缓存中有,直接返回
            log.info("query phone from redis");
            return ResultVo.success(value);
        }
        // 缓存中没有,查询数据不为空时,存入缓存
        TbPhone tbPhone = tbPhoneDao.queryById(id);
        if (!ObjectUtils.isEmpty(tbPhone)){
    
    
            log.info("query from mysql ,and save to redis");
//            redisService.setKey(keyGenorate(id), tbPhone);
            return ResultVo.success(tbPhone);
        }
        // 缓存和数据库都没有
        throw new Exception("query phone in redis and mysql is not exist");
    }
  1. View cache
    insert image description here

Guess you like

Origin blog.csdn.net/Proxbj/article/details/125821533