解决Redis序列化Java8 LocalDate、LocalDateTime等时间类报错

解决Redis序列化Java8 LocalDate、LocalDateTime等时间类报错

leo 3506 2021-06-16

前言

在使用 Redis 缓存时,Java 8 中的日期类序列化会报错。有以下两种解决方法:

Redis 配置类中添加对应序列化/反序列化器

@Slf4j
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    //过期时间 1 小时
    private Duration timeToLive = Duration.ofHours(1L);

    /**
     * RedisTemplate 相关配置
     *
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        // key采用String的序列化方式
        template.setKeySerializer(keySerializer());
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(keySerializer());
        // value序列化方式采用jackson
        template.setValueSerializer(valueSerializer());
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(valueSerializer());
        template.afterPropertiesSet();
        return template;
    }


    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        //配置 key value 序列化器,过期时间
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(timeToLive)
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
                .disableCachingNullValues();

        RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
        return redisCacheManager;
    }

    /**
     * key 序列化
     * @return
     */
    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }

    /**
     * 值采用JSON序列化
     * @return
     */
    private RedisSerializer<Object> valueSerializer() {
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        //LocalDate、LocalDateTime序列化器
        JavaTimeModule timeModule = new JavaTimeModule();
        timeModule.addDeserializer(LocalDate.class,
                new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addDeserializer(LocalDateTime.class,
                new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        timeModule.addSerializer(LocalDate.class,
                new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addSerializer(LocalDateTime.class,
                new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        om.registerModule(timeModule);

        jackson2JsonRedisSerializer.setObjectMapper(om);
        return jackson2JsonRedisSerializer;
    }

    /**
     * 处理缓存异常,不能影响正常业务
     * @return
     */
    @Override
    public CacheErrorHandler errorHandler() {
        return new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
                handleRedisCacheErrorException(exception, key);
            }

            @Override
            public void handleCachePutError(RuntimeException exception, Cache cache, Object key, Object value) {
                handleRedisCacheErrorException(exception, key);
            }

            @Override
            public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
                handleRedisCacheErrorException(exception, key);
            }

            @Override
            public void handleCacheClearError(RuntimeException exception, Cache cache) {
                handleRedisCacheErrorException(exception, null);
            }
        };
    }

    protected void handleRedisCacheErrorException(RuntimeException exception, Object key) {
        log.error("redis exception :key=[{}]", key, exception);
    }
}

字段上增加注解

实体类如下:在字段上添加 @JsonDeserialize(using = LocalDateDeserializer.class)@JsonSerialize(using = LocalDateSerializer.class)注解来指定序列化和反序列化器。

实体类

/**
 * @author Leo
 * @create 2020-07-17 10:36
 **/
@Data
@Accessors(chain = true)
public class Book implements Serializable {

    private static final long serialVersionUID = -444903545130170434L;

    private String id;

    private String name;

    private String summary;

    private Integer price;

//    @JsonDeserialize(using = LocalDateDeserializer.class)
//    @JsonSerialize(using = LocalDateSerializer.class)
//    @JsonFormat( pattern="yyyy-MM-dd")
    private LocalDate publishDate;
}

Service 类

/**
 * @author Leo
 * @create 2020-07-17 10:36
 **/
@Slf4j
@Service
public class BookService {

    private static List<Book> books = new ArrayList<>();

    static {
        for(int i = 0; i < 20; i++) {
            books.add(new Book()
                    .setId(String.valueOf(i))
                    .setName("Book" + i)
                    .setPrice(new Random().nextInt(200))
                    .setPublishDate(LocalDate.now()));
        }
    }

    @Cacheable(value = "books", key = "#root.methodName")
    public List<Book> selectAll() {
        log.info("selectAll from DB");
        return books;
    }

    @Cacheable(value = "books", key = "#id")
    public Book selectById(String id) {
        log.info("selectById from DB, id: " + id);
        return new Book().setId("999").setName("default").setPrice(100).setPublishDate(LocalDate.now());
    }

    @CacheEvict(value = "books", key = "#book.id")
    public void update(Book book) {
        log.info("update : " + book.toString());
    }

    @CacheEvict(value = "books", allEntries = true)
    public void delete(String id) {
        log.info("delete : " + id);
    }

}

测试

/**
 * @author Leo
 * @program redis-demo
 * @description
 * @create 2020-07-17 22:36
 **/
@Slf4j
@SpringBootTest(classes = RedisDemoApplication.class)
@RunWith(SpringRunner.class)
public class RedisDemoApplicationTests {

    @Resource
    private BookService bookService;

    @Test
    public void testSelectAll() {
        log.info("first selectAll");
        bookService.selectAll();
        log.info("second selectAll");
        bookService.selectAll();
    }

    @Test
    public void testSelectById() {
        //多次查询统一个id的数据
        bookService.selectById("0");
        bookService.selectById("0");
    }

    @Test
    public void testUpdate() {
        bookService.update(new Book().setId("0"));
//        bookService.selectAll();
    }

    @Test
    public void testDelete() {
        bookService.delete("0");
//        bookService.selectAll();
    }

}