SpringBoot 缓存之 @Cacheable使用示例

启动类在加上注解 @EnableCaching 开启缓存注解,主要适用于整个接口返回结果需要缓存的场景,其他情况,由于业务场景比较复杂,也就是单独某段代码需要缓存,使用redis的客户端会更加灵活。@Cacheable 的cacheNames可以理解为缓存key的前缀或者一级目录(redis可视化工具下)。

引入依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        
        <!--这边用的是Redis缓存,所以加上这个依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

@Configuration
@Slf4j
public class ActivityRedisConfig extends CachingConfigurerSupport {

    @Bean
    @SuppressWarnings(value = {"unchecked", "rawtypes"})
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        serializer.setObjectMapper(mapper);

        template.setValueSerializer(serializer);
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }


    /**
     * 使用入参的所有参数作为缓存的key
     * @return
     */
    @Bean
    public KeyGenerator activityKeyGenerator() {
        return (target, method, params) -> {
            System.out.println(params);
            String key = "";
            try {
// params[0] 为@RequestBody接收的对象,这里只针对这种情况做处理,
// 如果接口还有其他接收参数的形式,比如@pathvariable,
//@PathVariable结合@RequestParam等请自行测试再添加相关逻辑代码,这里就没去验证了
                key = getKey(params[0]);
            } catch (IllegalAccessException e) {
                log.info("缓存key生成失败", e);
            }
            return key;
        };
    }

    /**
     * 自定义缓存管理器
     */
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
        // 设置@cacheable 序列化方式,方便可视化工具查看
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer)).entryTtl(Duration.ofMinutes(5));
        Set<String> cacheNames = new HashSet<>();
        cacheNames.add("测试");
        cacheNames.add("distributor");
        ConcurrentHashMap<String, RedisCacheConfiguration> configMap = new ConcurrentHashMap<>();
        configMap.put("测试", config.entryTtl(Duration.ofMinutes(1L)));
        configMap.put("distributor", config.entryTtl(Duration.ofMinutes(50L)));
        //需要先初始化缓存名称,再初始化其它的配置。
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config)
                .initialCacheNames(cacheNames).withInitialCacheConfigurations(configMap).build();
        return cacheManager;
    }

    public String getKey(Object object) throws IllegalAccessException {
        String s = null;
        Field[] fields = object.getClass().getDeclaredFields();
        s = getField(fields, object, s);
        Field[] fieldsSuper = object.getClass().getSuperclass().getDeclaredFields();
        s = getField(fieldsSuper, object, s);
        return s;
    }

    public String getField(Field[] fields, Object object, String s) throws IllegalAccessException {
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            field.setAccessible(true);
            if (field.getName().equals("serialVersionUID")) continue;
            String ss = field.getName() + "_" + String.valueOf(field.get(object));
            s = s == null ? ss : s + "_" + ss;
        }
        return s;
    }


}

方式一:


    @PostMapping("users/info")
    @Cacheable(cacheNames = "测试", key = "#user.id", unless = "!#result?.result")
    @CapCode
    public ResultMessage info(@RequestBody User user) {
        System.out.println("controller[url=users/login]");
        return ResultMessage.success(user);
    }

方式二:自定义keyGenerator

    @PostMapping("users/info")
    @Cacheable(cacheNames = "测试", keyGenerator = "activityKeyGenerator", unless = "!#result?.result")
    @CapCode
    public ResultMessage info(@RequestBody User user) {
        System.out.println("controller[url=users/login]");
        return ResultMessage.success(user);
    }

其中unless的使用非常重要,决定什么情况下使用缓存,一般是接口查询成功才使用缓存,失败了就不需要缓存,具体使用看文章底部参考文章,另外说一点unless处理boolean类型时,表达式返回结果是false才缓存,上文接口处理成功,unless = "!#result?.result"。

返回封装如下:

public class ResultMessage<T> implements Serializable {
    /**
     *
     */
    private static final long serialVersionUID = 3443815263986524969L;

    public ResultMessage() {
    }

    /**
     * @param result
     * @param code
     * @param msg
     * @param data
     */
    public ResultMessage(Boolean result, int code, String msg, T data) {
        this.result = result;
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    /**
     * 结果,true 成功,false 失败
     */
    private Boolean result = true;

    /**
     * 编码
     */
    private int code;

    /**
     * 提示信息
     */
    private String msg;

    /**
     * 业务数据
     */
    private T data;

    /**
     * 成功
     *
     * @param msg
     * @param data
     * @return
     */
    public static <TT> ResultMessage<TT> success(String msg, TT data) {
        return new ResultMessage<TT>(Boolean.TRUE, Constants.SUCCESS_CODE, msg, data);
    }

    /**
     * 成功
     *
     * @param data
     * @return
     */
    public static <TT> ResultMessage<TT> success(TT data) {
        return new ResultMessage<TT>(Boolean.TRUE, Constants.SUCCESS_CODE, Constants.RET_SUCCESS_MESSAGE, data);
    }

    /**
     * 成功
     *
     * @param msg
     * @return
     */
    public static <TT> ResultMessage<TT> success(String msg) {
        return new ResultMessage<TT>(Boolean.TRUE, Constants.SUCCESS_CODE, msg, null);
    }

    /**
     * 失败
     *
     * @param msg
     * @param data
     * @return
     */
    public static <TT> ResultMessage<TT> fail(String msg, TT data) {
        return new ResultMessage<TT>(Boolean.FALSE, Constants.FAIL_CODE, msg, data);
    }

    /**
     * 失败
     *
     * @param data
     * @return
     */
    public static <TT> ResultMessage<TT> fail(TT data) {
        return new ResultMessage<TT>(Boolean.FALSE, Constants.FAIL_CODE, Constants.RET_FAIL_MESSAGE, data);
    }

    /**
     * 失败
     *
     * @param msg
     * @return
     */
    public static <TT> ResultMessage<TT> fail(String msg) {
        return new ResultMessage<TT>(Boolean.FALSE, Constants.FAIL_CODE, msg, null);
    }

    /**
     * 异常
     *
     * @param msg
     * @param data
     * @return
     */
    public static <TT> ResultMessage<TT> error(String msg, TT data) {
        return new ResultMessage<TT>(Boolean.FALSE, Constants.ERROR_CODE, msg, data);
    }

    /**
     * 异常
     *
     * @param data
     * @return
     */
    public static <TT> ResultMessage<TT> error(TT data) {
        return new ResultMessage<TT>(Boolean.FALSE, Constants.ERROR_CODE, Constants.RET_ERROR_MESSAGE, data);
    }

    /**
     * 异常
     *
     * @param msg
     * @return
     */
    public static <TT> ResultMessage<TT> error(String msg) {
        return new ResultMessage<TT>(Boolean.FALSE, Constants.ERROR_CODE, msg, null);
    }

    /**
     * 返回结果
     *
     * @param result
     * @param code
     * @param msg
     * @param data
     * @return
     */
    public static <TT> ResultMessage<TT> result(Boolean result, int code, String msg, TT data) {
        return new ResultMessage<TT>(result, code, msg, data);
    }

    public Boolean getResult() {
        return result;
    }

    public void setResult(Boolean result) {
        this.result = result;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "ResultMessage{" +
                "result=" + result +
                ", code=" + code +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }
}
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
    @SuppressWarnings("unused")
    private ObjectMapper objectMapper = new ObjectMapper();

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    static {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    }

    public FastJson2JsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz);
    }

    public void setObjectMapper(ObjectMapper objectMapper) {
        Assert.notNull(objectMapper, "'objectMapper' must not be null");
        this.objectMapper = objectMapper;
    }

    protected JavaType getJavaType(Class<?> clazz) {
        return TypeFactory.defaultInstance().constructType(clazz);
    }
}

参考:

redis@Cacheable注解unless用法

Springboot 之 @Cacheable 各种序列化及有效时间设置

@Cacheable注解是Spring框架提供的缓存注解,用于标记方法的返回结果可被缓存。它可以应用在方法级别或类级别。当方法被调用时,Spring会首先从缓存中查找方法的返回结果,如果缓存中存在,则直接返回缓存值,不再执行方法体内的逻辑。如果缓存中不存在,则执行方法体内的逻辑,并将返回结果存入缓存中。 @Cacheable注解默认是使用方法的参数作为缓存的key,所以相同参数调用的方法返回结果会被缓存起来。但是默认情况下,如果在缓存中找不到对应的结果,Spring会执行方法体内的逻辑,并将返回结果存入缓存中。这样会导致并发调用时出现缓存穿透问题,即多个线程同时请求同一个参数值,导致每个线程都执行了方法体内的逻辑,没有从缓存中获取到结果。 为了解决缓存穿透问题,可以使用热加载机制。热加载是指在缓存失效期间,只有一个线程去执行方法体内的逻辑,其他线程等待该线程执行完毕后直接从缓存中获取结果。 实现热加载可以通过在@Cacheable注解中设置sync属性为true。这样在缓存失效期间,只有一个线程去执行方法体内的逻辑,其他线程等待该线程执行完毕后直接从缓存中获取结果。示例代码如下: ```java @Cacheable(value = "myCache", key = "#param", sync = true) public String getData(String param) { // 执行业务逻辑 } ``` 需要注意的是,设置sync属性为true会导致性能损失,因为其他线程在等待期间无法直接从缓存中获取结果。因此,只有在必要的情况下才应该使用热加载机制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值