Spring Data Redis對(duì)象緩存序列化問題解決
相信在項(xiàng)目中,你一定是經(jīng)常使用 Redis ,那么,你是怎么使用的呢?在使用時(shí),有沒有遇到同我一樣,對(duì)象緩存序列化問題的呢?那么,你又是如何解決的呢?
Redis 使用示例
添加依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
在應(yīng)用啟動(dòng)如何添加啟用緩存注解(@EnableCaching
)。
假如我們有一個(gè)用戶對(duì)象(UserVo
):
@Data public class UserVo implements Serializable { @Serial private static final long serialVersionUID = 2215423070276994378L; private Long id; private String name; private LocalDateTime createDateTime; }
這里,我們實(shí)現(xiàn)了 Serializable
接口。
在我們需要緩存的方法上,使用 @Cacheable
注解,就表示如果返回的對(duì)象不是 null 時(shí),就會(huì)對(duì)其進(jìn)行緩存,下次查詢,首先會(huì)去緩存中查詢,查到了,就直接返回,不會(huì)再去數(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" )
模擬測(cè)試,再寫一個(gè)測(cè)試接口:
@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; } }
我們?cè)偌由线B接 redis 的配置:
spring: data: redis: host: localhost port: 6379
測(cè)試:
### getUserById GET http://localhost:8080/sample/user/1
輸出結(jié)果跟我們想的一樣,第一次從數(shù)據(jù)庫查,后面都從緩存直接返回。
總結(jié)一下:
添加
spring-boot-starter-data-redis
依賴。使用啟用緩存注解(
@EnableCaching
)。需要緩存的對(duì)象實(shí)現(xiàn)
Serializable
接口。使用
@Cacheable
注解緩存查詢的結(jié)果。
遇到問題
在上面我們通過 spring boot 提供的 redis 實(shí)現(xiàn)了查詢對(duì)象緩存這樣一個(gè)功能,有下面幾個(gè)問題:
- 緩存的對(duì)象,必須序列化,不然會(huì)報(bào)錯(cuò)。
- redis 存儲(chǔ)的數(shù)據(jù),看不懂,可以轉(zhuǎn)成 json 格式嗎?
- 使用 Jackson 時(shí),遇到特殊類型的字段會(huì)報(bào)錯(cuò),比如 LocalDateTime。
第1個(gè)問題,如果對(duì)象沒有實(shí)現(xiàn) Serializable
接口,會(huì)報(bào)錯(cuò):
關(guān)鍵信息:
java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [xxx.xxx.UserVo]
我詳細(xì)描述一下第3個(gè)問題,默認(rèn)是使用 Jdk序列化 JdkSerializationRedisSerializer
,redis 里面存的數(shù)據(jù)如下:
問題很明顯,對(duì)象必須要實(shí)現(xiàn)序列化接口,存的數(shù)據(jù)不易查看,所以,改用 GenericJackson2JsonRedisSerializer
,這就有了第3個(gè)問題。
我們加上下面的配置,就能解決第2個(gè)問題。
@Bean public RedisCacheConfiguration redisCacheConfiguration() { return RedisCacheConfiguration .defaultCacheConfig() .serializeValuesWith( RedisSerializationContext .SerializationPair .fromSerializer(RedisSerializer.json()) ); }
下面看第三個(gè)問題的錯(cuò)誤:
如何解決?
既然有了明確的錯(cuò)誤提示,那也是好解決的,我們可以這樣:
@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]}
其實(shí)到這里,已經(jīng)解決了問題,那有沒有更省心的辦法呢?
解決辦法
其實(shí)我們知道,使用的就是 Jackson 進(jìn)行 json 轉(zhuǎn)換,而 json 轉(zhuǎn)換,遇到 LocalDateTime 問題時(shí),我們配置一下 module 就可以了,因?yàn)槟J(rèn)用的 SimpleModule
,我們改用 JavaTimeModule
就可以了。
這時(shí)候問題又來啦,錯(cuò)誤如下:
這時(shí)候存的數(shù)據(jù)如下:
{"id":1,"name":"Zhang San","createDateTime":"2023-12-29T23:31:52.548517"}
這就涉及到 Jackson 序列化漏洞的問題了,采用了白名單機(jī)制,我們就粗暴一點(diǎn):
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); }
到此這篇關(guān)于Spring Data Redis對(duì)象緩存序列化問題解決的文章就介紹到這了,更多相關(guān)Spring Data Redis對(duì)象緩存序列化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot實(shí)現(xiàn)API接口多版本支持的示例代碼
這篇文章主要介紹了SpringBoot實(shí)現(xiàn)API接口多版本支持的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10詳解Java如何實(shí)現(xiàn)一個(gè)像String一樣不可變的類
說到?String?大家都知道?String?是一個(gè)不可變的類;雖然用的很多,那不知道小伙伴們有沒有想過怎么樣創(chuàng)建一個(gè)自己的不可變的類呢?這篇文章就帶大家來實(shí)踐一下,創(chuàng)建一個(gè)自己的不可變的類2022-11-11LambdaQueryWrapper的實(shí)現(xiàn)原理分析和lambda的序列化問題
這篇文章主要介紹了LambdaQueryWrapper的實(shí)現(xiàn)原理分析和lambda的序列化問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。2022-01-01SpringMVC接收與響應(yīng)json數(shù)據(jù)的幾種方式
這篇文章主要給大家介紹了關(guān)于SpringMVC接收與響應(yīng)json數(shù)據(jù)的幾種方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用springmvc具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03Java設(shè)計(jì)模式之狀態(tài)模式(State模式)介紹
這篇文章主要介紹了Java設(shè)計(jì)模式之狀態(tài)模式(State模式)介紹,本文講解了何時(shí)使用狀態(tài)模式、如何使用狀態(tài)模式等內(nèi)容,需要的朋友可以參考下2015-03-03HttpClient 在Java項(xiàng)目中的使用詳解
HttpClient作為訪問Http服務(wù)的客戶端訪問程序已經(jīng)被廣泛使用,提高了開發(fā)效率,也提高了代碼的健壯性。因此熟練掌握HttpClient是必需的,關(guān)于httpclient感興趣的朋友可以參考本篇文章2015-10-10