spring boot spring cache 使用redis多緩存策略的實現

​在項目中對數據的訪問往往都是直接訪問數據庫的方式,但如果對數據的訪問量很大或者訪問很頻繁的話,將會對數據庫來很大的壓力,甚至造成數據庫崩潰。爲了解決這類問題大家都會在項目中加入緩存。還有一種情況也是比較適合增加緩存的,數據的更新週期較長,很長時間不會改變。

對於springboot 項目,spring官方提供的默認緩存方案只支持一種緩存策略,既不同的緩存過期時間一致。因項目實際需要不同緩存不同的過期時間,所以對多緩存策略進行了研究。這裏採用redis作爲緩存容器,至於原因不再累贅。詳細過程如下

1. 安裝redis數據庫

2. 新建spring boot  2.1.7項目,pom文件如下:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.7.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>redis-cache</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>redis-cache</name>
<description>Demo project for Spring Boot</description>

<properties>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>
</dependencies>

 

3.啓動類增加@EnableCaching註解。

4.自定義配置文件讀取,代碼如下:

@ConfigurationProperties(prefix = "caching")
@Component
@Data
public class MyCacheProperties {

    private List<CacheSpec> specs;
    @Data
    static class CacheSpec {

        /**
         * 緩存過期時間
         */
        private Integer timeout;

        /**
         * 緩存名稱
         */
        private String cacheName;
    }
}

5. 自定義redis配置

@Configuration
@EnableCaching
@EnableConfigurationProperties(CacheProperties.class)
public class RedisConfig {


    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, MyCacheProperties properties,
                                          CacheProperties cacheProperties) {

        RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(defaultCacheConfig(cacheProperties));

        //自定義配置
        List<MyCacheProperties.CacheSpec> cacheSpecs = properties.getSpecs();
        if (!CollectionUtils.isEmpty(cacheSpecs)) {
            Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
            cacheSpecs.forEach(cacheSpec -> {
                RedisCacheConfiguration config = defaultCacheConfig(cacheProperties);
                if (cacheSpec.getTimeout() != null && StringUtils.isNoneEmpty(cacheSpec.getCacheName())) {
                    config = config.entryTtl(Duration.ofSeconds(cacheSpec.getTimeout()));
                    cacheConfigurations.put(cacheSpec.getCacheName(), config);
                }
            });
            builder = builder.withInitialCacheConfigurations(cacheConfigurations).transactionAware();
        }
        List<String> cacheNames = cacheProperties.getCacheNames();
        if (!cacheNames.isEmpty()) {
            builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
        }
        return builder.build();
    }

    /**
     * 默認緩存配置,使用spring cache 默認配置 也可在配置文件中修改spring cache緩存策略
     */
    private RedisCacheConfiguration defaultCacheConfig(CacheProperties cacheProperties) {
        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()));
        if (redisProperties.getTimeToLive() != null) {
            config = config.entryTtl(redisProperties.getTimeToLive());
        }
        if (redisProperties.getKeyPrefix() != null) {
            config = config.prefixKeysWith(redisProperties.getKeyPrefix());
        }
        if (!redisProperties.isCacheNullValues()) {
            config = config.disableCachingNullValues();
        }
        if (!redisProperties.isUseKeyPrefix()) {
            config = config.disableKeyPrefix();
        }
        return config;
    }


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

        redisTemplate.setKeySerializer(keySerializer());
        redisTemplate.setHashKeySerializer(keySerializer());
        redisTemplate.setValueSerializer(valueSerializer());
        redisTemplate.setHashValueSerializer(valueSerializer());
        return redisTemplate;
    }

    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }

    /**
     * 指定redis value的序列化方式爲jackson
     *
     */
    private RedisSerializer<Object> valueSerializer() {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        //設置輸入時忽略JSON字符串中存在而Java對象實際沒有的屬性
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        JavaTimeModule module = new JavaTimeModule();
        module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        module.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        module.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        objectMapper.registerModule(module);
        objectMapper.registerModule(new Jdk8Module());
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        return jackson2JsonRedisSerializer;
    }



    /**
     * 自定義緩存生成key
     */
    @Bean
    public KeyGenerator mKeyGenerator() {
        return (Object target, Method method, Object... params) -> {
            String key = target.getClass().getName().concat("|").concat(method.getName());
            StringBuilder stringBuilder = new StringBuilder(key);
            for (Object param : params) {
                if (!param.getClass().getName().startsWith("org.springframework")) {
                    stringBuilder.append("|");
                    stringBuilder.append(param.toString());
                }
            }
            return stringBuilder.toString();
        };
    }
}

 

6.新建測試controller 

@RestController
public class CacheTestController {


    @Cacheable(cacheNames = "test1", key = "method.name")
    @GetMapping("/test1")
    public Map<String, String> test1(HttpServletRequest request) {
        return getCacheOrMap(request);
    }

    @Cacheable(cacheNames = "test2", keyGenerator = "mKeyGenerator")
    @GetMapping("/test2")
    public Map<String, String> test2(HttpServletRequest request) {
        return getCacheOrMap(request);
    }

    private Map<String, String> getCacheOrMap(HttpServletRequest request) {
        request.getMethod();
        Map<String, String> result = new HashMap<>();
        result.put("method", request.getMethod());
        result.put("time", DateTimeFormatter.ISO_DATE_TIME.format(LocalDateTime.now()));
        return result;
    }

}

 

配置文件application.yml如下

server:
  port: 8090
spring:
  application:
    name: redis-cache
  redis:
    host: 127.0.0.1
    port: 6379
  cache:
#    指定緩存爲redis
    type: redis
caching:
  specs:
      #  test1 緩存過期時間60s
    - cacheName: test1
      timeout: 60
      #  test2 緩存過期時間10s
    - cacheName: test2

 

至此功能已經實現,訪問對應的接口即可進行測試。代碼地址: 

https://github.com/lbovery/redis-cache

其實相對來說沒那麼複雜,要實現這種非官方的自定義功能,建議大家多看源碼,瞭解spirng boot的原理,這樣以後不管遇到什麼問題都能迎刃而解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章