封裝一個redis獲取并解析數據的工具類

redis獲取并解析數據工具類

  • 實現代碼
  • 使用示例

實現代碼

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.Codec;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;@Slf4j
@Component
public class RedissonDataUtils {private static RedissonClient redissonClient;@Autowiredpublic void setRedissonClient(RedissonClient client) {RedissonDataUtils.redissonClient = client;}/*** 校驗 RedissonClient 是否已注入*/private static boolean clientReady() {if (redissonClient == null) {log.error("[RedissonDataUtils] RedissonClient 未注入,無法執行Redis操作");return false;}return true;}/*** 從 Redis 獲取數據,如果不存在或解析失敗則返回默認值** @param key          Redis 鍵* @param type         目標數據類型* @param defaultValue 默認值* @param <T>          數據類型* @return 解析后的數據對象或默認值*/public static <T> T getOrDefault(String key, Class<T> type, T defaultValue) {return get(key, type, null).orElse(defaultValue);}public static <T> T getOrDefault(String key, Class<T> type, T defaultValue, Codec codec) {return get(key, type, codec).orElse(defaultValue);}/*** 從 Redis 獲取數據,如果不存在或解析失敗則通過 Supplier 獲取默認值** @param key             Redis 鍵* @param type            目標數據類型* @param defaultSupplier 默認值提供者(延遲執行)* @param <T>             數據類型* @return 解析后的數據對象或默認值*/public static <T> T getOrDefaultSupplier(String key, Class<T> type, Supplier<T> defaultSupplier) {return get(key, type, null).orElseGet(defaultSupplier);}public static <T> T getOrDefaultSupplier(String key, Class<T> type, Supplier<T> defaultSupplier, Codec codec) {return get(key, type, codec).orElseGet(defaultSupplier);}/*** 從 Redis 獲取對象并進行安全解析* 1. 首選類型直返(與緩存編解碼一致時)* 2. 若為字符串則嘗試 JSON 反序列化* 3. 任何異常均捕獲并記錄,返回 Optional.empty()** @param key  緩存鍵* @param type 目標類型* @param <T>  泛型* @return Optional<T> 安全返回*/public static <T> Optional<T> get(String key, Class<T> type, Codec codec) {if (StrUtil.isBlank(key) || ObjectUtil.isNull(type)) {return Optional.empty();}if (!clientReady()) {return Optional.empty();}try {RBucket<Object> bucket;if (codec != null) {bucket = redissonClient.getBucket(key, codec);} else {bucket = redissonClient.getBucket(key);}Object cacheVal = bucket.get();if (cacheVal == null) {return Optional.empty();}T parsed = tryParse(cacheVal, type);return Optional.ofNullable(parsed);} catch (Exception e) {log.error("[RedissonDataUtils] 獲取并解析緩存異常, key={}, type={}, error:", key, type.getSimpleName(), e);return Optional.empty();}}public static <T> List<T> getList(String key, Class<T> elementType) {return getList(key, elementType, null);}/*** 從 Redis 獲取列表并進行安全解析** @param key         緩存鍵* @param elementType 列表元素類型* @param <T>         泛型* @return 不為 null,若無數據返回空列表*/public static <T> List<T> getList(String key, Class<T> elementType, Codec codec) {if (StrUtil.isBlank(key) || ObjectUtil.isNull(elementType)) {return Collections.emptyList();}if (!clientReady()) {return Collections.emptyList();}try {RBucket<Object> bucket;if (codec != null) {bucket = redissonClient.getBucket(key, codec);} else {bucket = redissonClient.getBucket(key);}Object cacheVal = bucket.get();if (cacheVal == null) {return Collections.emptyList();}// 優先處理已反序列化的列表if (cacheVal instanceof List) {try {@SuppressWarnings("unchecked")List<T> list = (List<T>) cacheVal;return CollUtil.isEmpty(list) ? Collections.emptyList() : list;} catch (ClassCastException ex) {// 繼續嘗試 JSON 解析}}// 處理字符串 JSONif (cacheVal instanceof String) {String json = (String) cacheVal;if (StrUtil.isBlank(json)) {return Collections.emptyList();}List<T> list = JSON.parseObject(json, new TypeReference<List<T>>() {});return CollUtil.isEmpty(list) ? Collections.emptyList() : list;}// 兜底:將對象轉字符串再嘗試解析String json = JSON.toJSONString(cacheVal);List<T> list = JSON.parseObject(json, new TypeReference<List<T>>() {});return CollUtil.isEmpty(list) ? Collections.emptyList() : list;} catch (Exception e) {log.error("[RedissonDataUtils] 獲取并解析列表緩存異常, key={}, elementType={}", key, elementType.getSimpleName(), e);return Collections.emptyList();}}/*** 獲取緩存,若不存在則通過回源函數加載并寫入緩存(帶可選 TTL)* 注意:此方法未加分布式鎖,若有并發回源風險,請在業務層配合 Redisson 鎖控制** @param key        緩存鍵* @param type       目標類型* @param loader     回源加載器,返回空時不寫緩存* @param ttlSeconds 過期秒數,<=0 表示不過期* @param <T>        泛型* @return 數據實例或 null* @example redissonDataUtils.getOrLoad("scrm:xx", CustomerDTO.class, () -> service.findById(0L).map(CustomerMapper::toDTO).orElse(null), 1800L);*/public static <T> T getOrLoad(String key, Class<T> type, Supplier<T> loader, long ttlSeconds, Codec codec) {// 先取緩存Optional<T> cached = get(key, type, codec);if (cached.isPresent()) {return cached.get();}if (!clientReady()) {// 客戶端未準備好時,直接回源,但不寫緩存try {return loader == null ? null : loader.get();} catch (Exception e) {log.error("[RedissonDataUtils] 回源加載異常(客戶端未就緒), key={}, type={}", key, type == null ? "null" : type.getSimpleName(), e);return null;}}// 回源加載T loaded;try {loaded = loader == null ? null : loader.get();} catch (Exception e) {log.error("[RedissonDataUtils] 回源加載異常, key={}, type={}", key, type == null ? "null" : type.getSimpleName(), e);return null;}if (loaded == null) {return null;}// 寫緩存(盡量容錯,不影響主流程)try {set(key, loaded, ttlSeconds, codec);} catch (Exception e) {log.warn("[RedissonDataUtils] 寫入緩存失敗(忽略), key={}", key, e);}return loaded;}/*** 寫入緩存(可設置過期時間)** @param key        緩存鍵* @param value      緩存值* @param ttlSeconds 過期秒數,<=0 表示不過期*/public static void set(String key, Object value, long ttlSeconds, Codec codec) {if (StrUtil.isBlank(key)) {return;}if (!clientReady()) {return;}try {RBucket<Object> bucket;if (codec != null) {bucket = redissonClient.getBucket(key, codec);} else {bucket = redissonClient.getBucket(key);}if (ttlSeconds > 0) {bucket.set(value, ttlSeconds, TimeUnit.SECONDS);} else {bucket.set(value);}} catch (Exception e) {log.error("[RedissonDataUtils] 寫入緩存異常, key={}", key, e);}}/*** 嘗試將緩存值解析為目標類型*/private static <T> T tryParse(Object cacheVal, Class<T> type) {if (cacheVal == null) {return null;}// 1) 已經是目標類型if (type.isInstance(cacheVal)) {return type.cast(cacheVal);}// 2) 字符串 JSONif (cacheVal instanceof String) {String json = (String) cacheVal;if (StrUtil.isBlank(json)) {return null;}return JSON.parseObject(json, type);}// 3) 兜底:轉 JSON 再解析String json = JSON.toJSONString(cacheVal);return JSON.parseObject(json, type);}
}

使用示例

  • 獲取對象,未獲取到使用默認值
// 1. 使用直接默認值
InfoVo info = RedissonDataUtils.getOrDefault("employee:1001", InfoVo.class, new InfoVo("默認員工", "默認部門")
);// 2. 使用 Supplier 默認值(延遲創建)
InfoVo Info = RedissonDataUtils.getOrDefault("employee:1001", InfoVo.class, () -> {InfoVo defaultVo = new InfoVo();defaultVo.setName("系統生成員工");defaultVo.setDepartment("技術部");defaultVo.setCreateTime(new Date());return defaultVo;}
);// 3. 基本類型示例
Integer userScore = RedissonDataUtils.getOrDefault("user:score:1001", Integer.class, 0
);// 4. 字符串示例
String userName = RedissonDataUtils.getOrDefault("user:name:1001", String.class, "匿名用戶"
);
  • 獲取對象
Optional<InfoVo> empOpt = RedissonDataUtils.get(rKey, InfoVo.class);
  • 獲取列表
List<InfoVo> list = RedissonDataUtils.getList(rKey, InfoVo.class);
  • 緩存未命中則回源并寫緩存(TTL 30 分鐘)
InfoVo vo = RedissonDataUtils.getOrLoad(rKey,InfoVo.class,() -> ucService.loadVo(req.getInfoId()),1800L
);
  • 寫緩存
RedissonDataUtils.set(rKey, infoVo, 1800L);

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/97846.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/97846.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/97846.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

23種設計模式——策略模式 (Strategy Pattern)?詳解

?作者簡介&#xff1a;大家好&#xff0c;我是 Meteors., 向往著更加簡潔高效的代碼寫法與編程方式&#xff0c;持續分享Java技術內容。 &#x1f34e;個人主頁&#xff1a;Meteors.的博客 &#x1f49e;當前專欄&#xff1a;設計模式 ?特色專欄&#xff1a;知識分享 &#x…

CI(持續集成)、CD(持續交付/部署)、CT(持續測試)、CICD、CICT

目錄 **CI、CD、CT 詳解與關系** **1. CI(Continuous Integration,持續集成)** **2. CD(Continuous Delivery/Deployment,持續交付/部署)** **持續交付(Continuous Delivery)** **持續部署(Continuous Deployment)** **3. CT(Continuous Testing,持續測試)** **4.…

【音視頻】WebRTC ICE 模塊深度剖析

原文鏈接&#xff1a; https://mp.weixin.qq.com/s?__bizMzIzMjY3MjYyOA&mid2247498075&idx2&sn6021a2f60b1e7c71ce4d7af6df0b9b89&chksme893e540dfe46c56323322e780d41aec1f851925cfce8b76b3f4d5cfddaa9c7cbb03a7ae4c25&scene178&cur_album_id314699…

linux0.12 head.s代碼解析

重新設置IDT和GDT&#xff0c;為256個中斷門設置默認的中斷處理函數檢查A20地址線是否啟用設置數學協處理器將main函數相關的參數壓棧設置分頁機制&#xff0c;將頁表映射到0~16MB的物理內存上返回main函數執行 源碼詳細注釋如下: /** linux/boot/head.s** (C) 1991 Linus T…

Maven動態控制版本號秘籍:高效發包部署,版本管理不再頭疼!

作者&#xff1a;唐叔在學習 專欄&#xff1a;唐叔的Java實踐 關鍵詞&#xff1a;Maven版本控制、versions插件、動態版本號、持續集成、自動化部署、Java項目管理 摘要&#xff1a;本文介紹如何使用Maven Versions插件動態控制項目版本號和依賴組件版本號&#xff0c;實現無需…

簡述:普瑞時空數據建庫軟件(國土變更建庫)之一(變更預檢查部分規則)

簡述&#xff1a;普瑞時空數據建庫軟件&#xff08;國土變更建庫&#xff09;之一(變更預檢查部分規則) 主要包括三種類型&#xff1a;常規檢查、行政區范圍檢查、20X異常滅失檢查 本blog地址&#xff1a;https://blog.csdn.net/hsg77

shell中命令小工具:cut、sort、uniq,tr的使用方式

提示&#xff1a;文章寫完后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 文章目錄前言一、cut —— 按列或字符截取1. 常用選項2. 示例二、sort —— 排序&#xff08;默認按行首字符升序&#xff09;1. 常用選項常用 sort 命令選項三、uniq —— 去…

【Linux】Linux開發必備:Git版本控制與GDB調試全指南

前言&#xff1a;在Linux開發流程中&#xff0c;版本控制與程序調試是保障項目穩定性和開發效率的兩大核心環節。Git作為當前最主流的分布式版本控制系統&#xff0c;能高效管理代碼迭代、追蹤修改記錄并支持多人協同開發&#xff1b;GDB&#xff08;GNU調試器&#xff09;是Li…

實現 TypeScript 內置工具類型(源碼解析與實現)

目標讀者&#xff1a;已經熟悉 TypeScript 基礎語法、泛型、條件類型的同學。本文按常見工具類型的分類與順序實現并解釋 Partial、Required、Readonly、Pick、Omit、Record、Exclude、Extract、NonNullable、ReturnType、Parameters、ConstructorParameters、InstanceType、Th…

Spring Boot + Nacos 配置中心示例工程

1?? 工程結構 nacos-demo├── pom.xml└── src├── main│ ├── java│ │ └── com.example.nacosdemo│ │ ├── NacosDemoApplication.java│ │ ├── config│ │ │ └── AppProperties.java│ │ └── cont…

(二)文件管理-基礎命令-pwd命令的使用

文章目錄1. 命令格式2. 基本用法3. 高級用法4. 注意事項1. 命令格式 pwd [OPTION]...[OPTION]: 可選選項&#xff0c;用于改變命令的默認行為。最主要的兩個選項是 -L 和 -P。它不需要任何參數&#xff08;如文件名或目錄名&#xff09; 2. 基本用法 用法&#xff1a;pwd 是…

Leetcode_202.快樂數_三種方法解決(普通方法解決,哈希表解決,循環鏈表的性質解決_快慢指針)

目錄第一種方法&#xff1a;暴力解法暴力ac代碼&#xff1a;第二種方法&#xff1a;哈希表哈希表ac代碼:第三種方法&#xff1a;根據循環鏈表的性質(快慢指針)第一種方法&#xff1a;暴力解法 最暴力的思路就是直接使用循環往下一直計算&#xff0c;這樣特別浪費時間&#xff…

代碼隨想錄刷題Day48

這次博客主要是對做過的關于二叉樹系列的題目進行整理和分類。二叉樹&#xff0c;要處理整個樹&#xff0c;一般少不了遍歷。遍歷主要可以分為&#xff1a;遞歸系列、層序遍歷。如果不遍歷的話&#xff0c;那就是處理特殊的樹了&#xff0c;比如完全二叉樹。遞歸系列基本的遞歸…

汽車工裝結構件3D掃描尺寸測量公差比對-中科米堆CASAIM

汽車制造過程中&#xff0c;工裝結構件的尺寸精度對整車裝配質量和生產進度有重要影響。傳統測量工具如卡尺和三坐標測量機采用接觸式工作方式&#xff0c;檢測過程耗時較長&#xff0c;對于具有復雜曲面特征的工件&#xff0c;難以全面獲取尺寸數據。激光三維掃描技術改變了傳…

Docker Pull 代理配置方法

本文介紹通過網絡代理加速Docker鏡像拉取的方法。 配置方法 當執行docker pull從Docker Hub 拉取鏡像時&#xff0c;其網絡連接由守護進程docker daemon進行維護。 要修改其代理設置&#xff0c;可配置其systemd服務&#xff0c;步驟如下&#xff1a; &#xff08;1&#xf…

機電裝置:從基礎原理到前沿應用的全方位解析

本文由「大千AI助手」原創發布&#xff0c;專注用真話講AI&#xff0c;回歸技術本質。拒絕神話或妖魔化。搜索「大千AI助手」關注我&#xff0c;一起撕掉過度包裝&#xff0c;學習真實的AI技術&#xff01; 1 機電裝置的基本概念與發展歷程 機電裝置&#xff08;Mechatronic D…

《SVA斷言系統學習之路》【03】關于布爾表達式

序列中使用的表達式基于其所含變量的采樣值進行評估。表達式評估的結果為布爾值&#xff0c;其解釋方式與過程性if語句條件中的表達式完全相同&#xff1a;若表達式計算結果為X、Z 或 0&#xff0c;則被解釋為假&#xff1b;否則即為真。但是&#xff0c;對可出現在并發斷言中的…

指針高級(2)

6.數組指針#include <stdio.h> int main() {/*練習&#xff1a;利用指針遍歷數組*///1.定義數組int arr[] { 10,20,30,40,50 };int len sizeof(arr) / sizeof(int);//2.獲取數組的指針//實際上獲取的&#xff1a;數組的首地址int* p1 arr;int* p2 &arr[0];printf…

如何高效記單詞之:抓住首字母——以find、fund、fond、font為例

find、fund、fond、font這幾個單詞&#xff0c;你都認識嗎&#xff1f;這幾個單詞&#xff0c;意思大體如下&#xff1a; find v.找到&#xff1b;發現fund n.基金fond a.喜歡的&#xff1b;喜愛的&#xff1b;深情的font n.字體&#xff0c;字型&#xff0c;字形 這幾個單詞在…

Ubuntu下把 SD 卡格式化為 FAT32

在 Ubuntu 下把 SD 卡格式化為 FAT32&#xff0c;按下面做&#xff08;會抹掉整卡數據??&#xff09;&#xff1a; 1) 找到你的 SD 卡設備名 lsblk -p記下整盤設備&#xff0c;比如 /dev/sdb&#xff08;USB 讀卡器常見&#xff09;或 /dev/mmcblk0&#xff08;內置讀卡器&am…