Spring Data Redis對象緩存序列化問題解決
相信在項目中,你一定是經(jīng)常使用 Redis ,那么,你是怎么使用的呢?在使用時,有沒有遇到同我一樣,對象緩存序列化問題的呢?那么,你又是如何解決的呢?
Redis 使用示例
添加依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在應用啟動如何添加啟用緩存注解(@EnableCaching)。
假如我們有一個用戶對象(UserVo):
@Data
public class UserVo implements Serializable {
@Serial
private static final long serialVersionUID = 2215423070276994378L;
private Long id;
private String name;
private LocalDateTime createDateTime;
}
這里,我們實現(xiàn)了 Serializable 接口。
在我們需要緩存的方法上,使用 @Cacheable 注解,就表示如果返回的對象不是 null 時,就會對其進行緩存,下次查詢,首先會去緩存中查詢,查到了,就直接返回,不會再去數(shù)據(jù)庫查詢,查不到,再去數(shù)據(jù)庫查詢。
@Service
@Slf4j
public class UserServiceImpl implements IUserService {
@Override
@Cacheable(
value = "sample-redis",
key = "'user-'+#id",
unless = "#result == null"
)
public UserVo getUserById(Long id) {
log.info("userVo from db query");
UserVo userVo = new UserVo();
userVo.setId(1L);
userVo.setName("Zhang San");
userVo.setCreateDateTime(LocalDateTime.now());
return userVo;
}
}
核心代碼:
@Cacheable(
value = "sample-redis",
key = "'user-'+#id",
unless = "#result == null"
)
模擬測試,再寫一個測試接口:
@RestController
@RequestMapping("/sample")
@RequiredArgsConstructor
@Slf4j
public class SampleController {
private final IUserService userService;
@GetMapping("/user/{id}")
public UserVo getUserById(@PathVariable Long id) {
UserVo vo = userService.getUserById(id);
log.info("vo: {}", JacksonUtils.json(vo));
return vo;
}
}
我們再加上連接 redis 的配置:
spring:
data:
redis:
host: localhost
port: 6379
測試:
### getUserById GET http://localhost:8080/sample/user/1

輸出結(jié)果跟我們想的一樣,第一次從數(shù)據(jù)庫查,后面都從緩存直接返回。
總結(jié)一下:
添加
spring-boot-starter-data-redis依賴。使用啟用緩存注解(
@EnableCaching)。需要緩存的對象實現(xiàn)
Serializable接口。使用
@Cacheable注解緩存查詢的結(jié)果。
遇到問題
在上面我們通過 spring boot 提供的 redis 實現(xiàn)了查詢對象緩存這樣一個功能,有下面幾個問題:
- 緩存的對象,必須序列化,不然會報錯。
- redis 存儲的數(shù)據(jù),看不懂,可以轉(zhuǎn)成 json 格式嗎?
- 使用 Jackson 時,遇到特殊類型的字段會報錯,比如 LocalDateTime。
第1個問題,如果對象沒有實現(xiàn) Serializable 接口,會報錯:

關鍵信息:
java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [xxx.xxx.UserVo]
我詳細描述一下第3個問題,默認是使用 Jdk序列化 JdkSerializationRedisSerializer,redis 里面存的數(shù)據(jù)如下:

問題很明顯,對象必須要實現(xiàn)序列化接口,存的數(shù)據(jù)不易查看,所以,改用 GenericJackson2JsonRedisSerializer ,這就有了第3個問題。
我們加上下面的配置,就能解決第2個問題。
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
return RedisCacheConfiguration
.defaultCacheConfig()
.serializeValuesWith(
RedisSerializationContext
.SerializationPair
.fromSerializer(RedisSerializer.json())
);
}
下面看第三個問題的錯誤:

如何解決?
既然有了明確的錯誤提示,那也是好解決的,我們可以這樣:
@JsonDeserialize(using = LocalDateTimeDeserializer.class) // 反序列化 @JsonSerialize(using = LocalDateTimeSerializer.class) // 序列化 private LocalDateTime createDateTime;
這樣就可以了,我們看下redis里面存的數(shù)據(jù):
{"@class":"com.fengwenyi.erwin.component.sample.redis.vo.UserVo","id":1,"name":"Zhang San","createDateTime":[2023,12,29,23,44,3,479011000]}
其實到這里,已經(jīng)解決了問題,那有沒有更省心的辦法呢?
解決辦法
其實我們知道,使用的就是 Jackson 進行 json 轉(zhuǎn)換,而 json 轉(zhuǎn)換,遇到 LocalDateTime 問題時,我們配置一下 module 就可以了,因為默認用的 SimpleModule,我們改用 JavaTimeModule 就可以了。
這時候問題又來啦,錯誤如下:

這時候存的數(shù)據(jù)如下:
{"id":1,"name":"Zhang San","createDateTime":"2023-12-29T23:31:52.548517"}
這就涉及到 Jackson 序列化漏洞的問題了,采用了白名單機制,我們就粗暴一點:
jsonMapper.activateDefaultTyping( LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL );
redis 存的數(shù)據(jù)如下:
["com.fengwenyi.erwin.component.sample.redis.vo.UserVo",{"id":1,"name":"Zhang San","createDateTime":"2023-12-29T23:56:18.197203"}]
最后,來一段完整的 RedisCacheConfiguration 配置代碼:
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
return RedisCacheConfiguration
.defaultCacheConfig()
.serializeValuesWith(
RedisSerializationContext
.SerializationPair
// .fromSerializer(RedisSerializer.json())
// .fromSerializer(
// new GenericJackson2JsonRedisSerializer()
// )
.fromSerializer(redisSerializer())
);
}
private RedisSerializer<Object> redisSerializer() {
JsonMapper jsonMapper = new JsonMapper();
JacksonUtils.configure(jsonMapper);
jsonMapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL
);
return new GenericJackson2JsonRedisSerializer(jsonMapper);
}
到此這篇關于Spring Data Redis對象緩存序列化問題解決的文章就介紹到這了,更多相關Spring Data Redis對象緩存序列化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot實現(xiàn)API接口多版本支持的示例代碼
這篇文章主要介紹了SpringBoot實現(xiàn)API接口多版本支持的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-10-10
詳解Java如何實現(xiàn)一個像String一樣不可變的類
說到?String?大家都知道?String?是一個不可變的類;雖然用的很多,那不知道小伙伴們有沒有想過怎么樣創(chuàng)建一個自己的不可變的類呢?這篇文章就帶大家來實踐一下,創(chuàng)建一個自己的不可變的類2022-11-11
LambdaQueryWrapper的實現(xiàn)原理分析和lambda的序列化問題
這篇文章主要介紹了LambdaQueryWrapper的實現(xiàn)原理分析和lambda的序列化問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教。2022-01-01
SpringMVC接收與響應json數(shù)據(jù)的幾種方式
這篇文章主要給大家介紹了關于SpringMVC接收與響應json數(shù)據(jù)的幾種方式,文中通過示例代碼介紹的非常詳細,對大家的學習或者使用springmvc具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧2019-03-03

