Spring Data Redis對象緩存序列化問題

相信在項目中,你一定是經常使用 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;

}

這里,我們實現了 Serializable 接口。

在我們需要緩存的方法上,使用 @Cacheable 注解,就表示如果返回的對象不是 null 時,就會對其進行緩存,下次查詢,首先會去緩存中查詢,查到了,就直接返回,不會再去數據庫查詢,查不到,再去數據庫查詢。

@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


image-20231229232659949.png

輸出結果跟我們想的一樣,第一次從數據庫查,后面都從緩存直接返回。

總結一下:

  1. 添加 spring-boot-starter-data-redis 依賴。

  2. 使用啟用緩存注解(@EnableCaching)。

  3. 需要緩存的對象實現 Serializable 接口。

  4. 使用 @Cacheable 注解緩存查詢的結果。

遇到問題

在上面我們通過 spring boot 提供的 redis 實現了查詢對象緩存這樣一個功能,有下面幾個問題:

  1. 緩存的對象,必須序列化,不然會報錯。
  2. redis 存儲的數據,看不懂,可以轉成 json 格式嗎?
  3. 使用 Jackson 時,遇到特殊類型的字段會報錯,比如 LocalDateTime。

第1個問題,如果對象沒有實現 Serializable接口,會報錯:

image-20231230230844373.png

關鍵信息:

java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [xxx.xxx.UserVo]

我詳細描述一下第3個問題,默認是使用 Jdk序列化 JdkSerializationRedisSerializer,redis 里面存的數據如下:

image-20231229232824285.png

問題很明顯,對象必須要實現序列化接口,存的數據不易查看,所以,改用 GenericJackson2JsonRedisSerializer ,這就有了第3個問題。

我們加上下面的配置,就能解決第2個問題。

@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
    return RedisCacheConfiguration
            .defaultCacheConfig()
            .serializeValuesWith(
                    RedisSerializationContext
                            .SerializationPair
                            .fromSerializer(RedisSerializer.json())
            );
}

下面看第三個問題的錯誤:

image-20231229233716885.png

如何解決?

既然有了明確的錯誤提示,那也是好解決的,我們可以這樣:

@JsonDeserialize(using = LocalDateTimeDeserializer.class)       // 反序列化
@JsonSerialize(using = LocalDateTimeSerializer.class)           // 序列化
private LocalDateTime createDateTime;

這樣就可以了,我們看下redis里面存的數據:

{"@class":"com.fengwenyi.erwin.component.sample.redis.vo.UserVo","id":1,"name":"Zhang San","createDateTime":[2023,12,29,23,44,3,479011000]}

其實到這里,已經解決了問題,那有沒有更省心的辦法呢?

解決辦法

其實我們知道,使用的就是 Jackson 進行 json 轉換,而 json 轉換,遇到 LocalDateTime 問題時,我們配置一下 module 就可以了,因為默認用的 SimpleModule,我們改用 JavaTimeModule 就可以了。

這時候問題又來啦,錯誤如下:

image-20231229233248619.png

這時候存的數據如下:

{"id":1,"name":"Zhang San","createDateTime":"2023-12-29T23:31:52.548517"}

這就涉及到 Jackson 序列化漏洞的問題了,采用了白名單機制,我們就粗暴一點:

jsonMapper.activateDefaultTyping(
  LaissezFaireSubTypeValidator.instance, 
  ObjectMapper.DefaultTyping.NON_FINAL
);

redis 存的數據如下:

["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);
}

希望今天的分享對你有一定的幫助。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,530評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,759評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,204評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,415評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,955評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,675評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容