優雅的緩存解決方案--設置過時時間

1. 前言

上篇文章介紹了利用 SpringCache 和 Redis 設置緩存,可是SpringCache 註解並不支持設置緩存時間,確實很使人頭疼。這篇文章將叫你用最簡單的方式解決 SpringCache 和 Redis 設置緩存並設置緩存時間。 此篇文章基於上篇博客,有啥不懂的地方請查看上篇博客。 上篇文章連接:優雅的緩存解決方案--SpringCache和Redis集成(SpringBoot)html

2. 配置

@Cacheable註解不支持配置過時時間,全部須要經過配置CacheManneg來配置默認的過時時間和針對每一個類或者是方法進行緩存失效時間配置。java

解決   能夠採用以下的配置信息來解決的設置失效時間問題配置信息git

修改配置類

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
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.cache.RedisCacheWriter;
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 java.io.Serializable;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/** * @Author: MaoLin * @Date: 2019/3/26 17:04 * @Version 1.0 */

@Configuration
@EnableCaching
public class RedisConfig implements Serializable {

     /** * 申明緩存管理器,會建立一個切面(aspect)並觸發Spring緩存註解的切點(pointcut) * 根據類或者方法所使用的註解以及緩存的狀態,這個切面會從緩存中獲取數據,將數據添加到緩存之中或者從緩存中移除某個值 */

   /* @Bean public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { return RedisCacheManager.create(redisConnectionFactory); } @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory) { // 建立一個模板類 RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); // 將剛纔的redis鏈接工廠設置到模板類中 template.setConnectionFactory(factory); // 設置key的序列化器 template.setKeySerializer(new StringRedisSerializer()); // 設置value的序列化器 //使用Jackson 2,將對象序列化爲JSON Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); //json轉對象類,不設置默認的會將json轉成hashmap ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setValueSerializer(jackson2JsonRedisSerializer); return template; }*/


    /** * 最新版,設置redis緩存過時時間 */

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        return new RedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), this.getRedisCacheConfigurationWithTtl( 60), this.getRedisCacheConfigurationMap() // 指定 key 策略
        );
    }

    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
        //SsoCache和BasicDataCache進行過時時間配置
     redisCacheConfigurationMap.put("messagCache", this.getRedisCacheConfigurationWithTtl(30 * 60));   redisCacheConfigurationMap.put("userCache", this.getRedisCacheConfigurationWithTtl(60));//自定義設置緩存時間
    
        return redisCacheConfigurationMap;
    }

    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
        Jackson2JsonRedisSerializer<Object> 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);
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
                RedisSerializationContext
                        .SerializationPair
                        .fromSerializer(jackson2JsonRedisSerializer)
        ).entryTtl(Duration.ofSeconds(seconds));

        return redisCacheConfiguration;
    }
}
複製代碼

測試

  • 設置緩存名稱及緩存時間(以下爲60秒)
redisCacheConfigurationMap.put("userCache",this.getRedisCacheConfigurationWithTtl(60));
複製代碼
  • 使用 加上註解便可 @Cacheable("userCache") 注:名稱爲配置類裏面設置的名稱userCache,可設置多個緩存名稱及時間

Controller測試類github

import com.ml.demo.dao.UserDao;
import com.ml.demo.entity.User;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.io.Serializable;

/** * @Author: MaoLin * @Date: 2019/3/26 17:03 * @Version 1.0 */


@RestController
public class testController implements Serializable {
    @Resource
    private UserDao userDao;

    /** * 查詢出一條數據而且添加到緩存 * * @param userId * @return */
    @RequestMapping("/getUser")
    @Cacheable("userCache")
    public User getUser(@RequestParam(required = true) String userId) {
        System.out.println("若是沒有緩存,就會調用下面方法,若是有緩存,則直接輸出,不會輸出此段話");
        return userDao.getUser(Integer.parseInt(userId));
    }

    /** * 刪除一個緩存 * * @param userId * @return */
    @RequestMapping(value = "/deleteUser")
    @CacheEvict("userCache")
    public String deleteUser(@RequestParam(required = true) String userId) {
        return "刪除成功";
    }

    /** * 添加一條保存的數據到緩存,緩存的key是當前user的id * * @param user * @return */
    @RequestMapping("/saveUser")
    @CachePut(value = "userCache", key = "#result.userId +''")
    public User saveUser(User user) {
        return user;
    }


    /** * 返回結果userPassword中含有nocache字符串就不緩存 * * @param userId * @return */
    @RequestMapping("/getUser2")
    @CachePut(value = "userCache", unless = "#result.userPassword.contains('nocache')")
    public User getUser2(@RequestParam(required = true) String userId) {
        System.out.println("若是走到這裏說明,說明緩存沒有生效!");
        User user = new User(Integer.parseInt(userId), "name_nocache" + userId, "nocache");
        return user;
    }


    @RequestMapping("/getUser3")
    @Cacheable(value = "userCache", key = "#root.targetClass.getName() + #root.methodName + #userId")
    public User getUser3(@RequestParam(required = true) String userId) {
        System.out.println("若是第二次沒有走到這裏說明緩存被添加了");
        return userDao.getUser(Integer.parseInt(userId));
    }

}

複製代碼

測試運行及結果web

  • 保存緩存 redis

  • 查看緩存 spring

  • 查看redis json

  • 一分鐘後緩存過時 緩存

  • 再查詢緩存 bash

  • 控制檯運行結果

3. 報錯解決

2019-03-31 14:21:05.163 ERROR 17056 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Cannot construct instance of `com.ml.demo.entity.User` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (byte[])"["com.ml.demo.entity.User",{"userId":11,"userName":"\"張三\"","userPassword":"123"}]"; line: 1, column: 29]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.ml.demo.entity.User` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (byte[])"["com.ml.demo.entity.User",{"userId":11,"userName":"\"張三\"","userPassword":"123"}]"; line: 1, column: 29]] with root cause 複製代碼

這個 bug 調了很久才解決,其實問題很簡單。

緣由:

緣由是我在該實體類中添加了一個爲了方便實例化該類用的構造函數,致使JVM不會添加默認的無參構造函數,而jackson的反序列化須要無參構造函數,所以報錯。

Response實體類同理。

解決:

在實體類中補上一個無參構造器便可。

public User() {}

小結&參考資料

小結

利用 Spring 提供的緩存機制(對象)結合Redis 實現緩存實際上是很好的方法,可是沒有提供設置緩存時間,這個就很不人性化了,Redis 的使用其實 Spring 還提供了 RedisTemplate 和 StringRedisTemplate 這兩個類都支持設置緩存時間,若是要是以爲 SpringCache 的使用不太方便,能夠利用 RedisTemplate 類自定義 Redis 工具類來實現緩存。

參考資料

相關文章
相關標籤/搜索