@Cacheable 注解在方法上,表示该方法的返回结果是可以缓存的。也就是说,该方法的返回结果会放在缓存中,以便于以后使用相同的参数调用该方法时,会返回缓存中的值,而不会实际执行该方法。

基本的概念可以查看这篇文档,本次是因为用到了Cacheable注解,记录一下使用过程

主要的几点:

开启基于注解的缓存,使用 @EnableCaching 标注在 springboot 主启动类上

  1. package com.insigma;
  2. import com.insigma.common.security.annotation.EnableCustomConfig;
  3. import com.insigma.common.security.annotation.EnableRyFeignClients;
  4. import com.spring4all.swagger.EnableSwagger2Doc;
  5. import org.springframework.boot.SpringApplication;
  6. import org.springframework.cloud.client.SpringCloudApplication;
  7. @SpringCloudApplication
  8. @EnableSwagger2Doc
  9. @EnableRyFeignClients
  10. @EnableCustomConfig
  11. public class AppSysDict {
  12. public static void main(String[] args) {
  13. SpringApplication.run(AppSysDict.class,args);
  14. System.out.println("字典服务启动成功");
  15. }
  16. }
  1. package com.insigma.common.security.annotation;
  2. import com.insigma.common.security.config.ApplicationConfig;
  3. import com.insigma.common.security.feign.FeignAutoConfiguration;
  4. import org.mybatis.spring.annotation.MapperScan;
  5. import org.springframework.cache.annotation.EnableCaching;
  6. import org.springframework.context.annotation.EnableAspectJAutoProxy;
  7. import org.springframework.context.annotation.Import;
  8. import org.springframework.scheduling.annotation.EnableAsync;
  9. import java.lang.annotation.*;
  10. /**
  11. * @author Jiangchao
  12. * @description: TODO
  13. * @date 2021/9/29 14:43
  14. */
  15. @Target(ElementType.TYPE)
  16. @Retention(RetentionPolicy.RUNTIME)
  17. @Documented
  18. @Inherited
  19. // 表示通过aop框架暴露该代理对象,AopContext能够访问
  20. @EnableAspectJAutoProxy(exposeProxy = true)
  21. // 指定要扫描的Mapper类的包的路径
  22. @MapperScan("com.insigma.**.mapper")
  23. // 开启线程异步执行
  24. @EnableAsync
  25. //开启缓存
  26. @EnableCaching
  27. // 自动加载类
  28. @Import({ ApplicationConfig.class, FeignAutoConfiguration.class })
  29. public @interface EnableCustomConfig {
  30. }

标注缓存注解

最好标注在实现类上,标注在接口上会有些问题,具体问题没细究,测试的时候好像会跟其他注解发送冲突,可以看看这篇文章

    @Override
    @Cacheable(value = DictConstant.SYS_DICT_CACHE, key = "#sysDictInDTO.dictCode+':'+#sysDictInDTO.dictValue", unless = "#result == null")
    public SysDictOutDTO queryDictByCodeAndValue(SysDictInDTO sysDictInDTO) {
        SysDictOutDTO sysDictOutDTO = sysDictMapper.queryDictByCodeAndValue(sysDictInDTO);
        if (sysDictOutDTO == null){
            return null;
        }
        sysDictOutDTO.setDictCode(sysDictInDTO.getDictCode());

        return sysDictOutDTO;
    }

image.pngimage.png

还要声明一个cacheManager,数据缓存在哪

package com.insigma.common.redis.configure;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
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.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes", "deprecation" })
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory)
    {
        RedisTemplate<String, 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.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);

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


    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {

        RedisSerializer<String> strSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSeial.setObjectMapper(om);
        RedisCacheConfiguration config =
                RedisCacheConfiguration.defaultCacheConfig()
                        .entryTtl(Duration.ofDays(1))
                        .serializeKeysWith(RedisSerializationContext.SerializationPair
                                .fromSerializer(strSerializer))
                        .serializeValuesWith(RedisSerializationContext.SerializationPair
                                .fromSerializer(jacksonSeial))
                        .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager
                .builder(redisConnectionFactory).cacheDefaults(config).build();
        return cacheManager; 
    }
}

注意点

由于注解是由spring aop实现的,所以在类本身内部调用的时候,如果直接调用方法,Cacheable注解是不生效的,需要先把自己注入进来

package com.insigma.sysdict.service.api.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.insigma.common.core.constants.DictConstant;
import com.insigma.common.core.domain.BaseResponse;
import com.insigma.common.core.utils.StringUtils;
import com.insigma.common.core.utils.bean.BeanUtils;
import com.insigma.common.redis.service.RedisService;
import com.insigma.sysdict.dto.input.SysDictInDTO;
import com.insigma.sysdict.dto.output.DictOutDTO;
import com.insigma.sysdict.dto.output.SysDictOutDTO;
import com.insigma.sysdict.entity.SysDict;
import com.insigma.sysdict.entity.SysDictItem;
import com.insigma.sysdict.mapper.SysDictItemMapper;
import com.insigma.sysdict.mapper.SysDictMapper;
import com.insigma.sysdict.service.api.SysDictService;
import com.insigma.sysdict.strategy.context.SysDictStrategyContext;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@RestController
@Slf4j
public class SysDictServiceImpl implements SysDictService {
    @Resource
    private SysDictMapper sysDictMapper;
    @Resource
    private SysDictService sysDictService;

    @Override
    @Cacheable(value = DictConstant.SYS_DICT_CACHE, key = "#sysDictInDTO.dictCode+':'+#sysDictInDTO.dictValue", unless = "#result == null")
    public SysDictOutDTO queryDictByCodeAndValue(SysDictInDTO sysDictInDTO) {
        SysDictOutDTO sysDictOutDTO = sysDictMapper.queryDictByCodeAndValue(sysDictInDTO);
        if (sysDictOutDTO == null){
            return null;
        }
        sysDictOutDTO.setDictCode(sysDictInDTO.getDictCode());

        return sysDictOutDTO;
    }

    @Override
    public List<SysDictOutDTO> queryDictListByCodeAndValue(List<SysDictInDTO> sysDictInDTOList) {
        if (CollectionUtils.isEmpty(sysDictInDTOList)){
            return null;
        }

        List<SysDictOutDTO> sysDictOutDTOList = new ArrayList<>();
        for (SysDictInDTO sysDictInDTO : sysDictInDTOList) {
            // 先查询redis
            String key = DictConstant.SYS_DICT_CACHE + "::" + sysDictInDTO.getDictCode() + ":" + sysDictInDTO.getDictValue();
            Object cacheObject = redisService.getCacheObject(key);
            SysDictOutDTO sysDictOutDTO;
            List list = new ArrayList();
            if (cacheObject == null){
                // 查询数据库
                // 调用自身方法,并且Cacheable注解还能生效
                sysDictOutDTO = sysDictService.queryDictByCodeAndValue(sysDictInDTO);
                if (sysDictOutDTO == null){
                    continue;
                }
            }else {
                list = JSONObject.parseArray(JSON.toJSONString(cacheObject));
                if (CollectionUtils.isNotEmpty(list) && list.size() > 1){
                    sysDictOutDTO = JSONObject.parseObject(JSON.toJSONString(list.get(1)), SysDictOutDTO.class);
                }else {
                    continue;
                }
            }

            sysDictOutDTOList.add(sysDictOutDTO);
        }
        return sysDictOutDTOList;
    }
}