網站首頁 編程語言 正文
將 StringRedisTemplate 封裝成一個緩存工具類,方便以后重復使用。
1. 方法要求
在這個工具類中我們完成四個方法:
- 方法①:將任意Java對象序列化為json并存儲在string類型的key中,并且可以設置TTL過期時間
- 方法②:將任意Java對象序列化為json并存儲在string類型的key中,并且可以設置邏輯過期時間,用于處理緩存擊穿問題
- 方法③:根據指定的key查詢緩存,并反序列化為指定類型,利用緩存空值的方式解決緩存穿透問題
- 方法④:根據指定的key查詢緩存,并反序列化為指定類型,需要利用邏輯過期解決緩存擊穿問題
我們新建一個類,先把大致框架寫出來,方法的參數可以邊寫邊完善,但是我的方法參數已經完善好了:
@Component
public class CacheClient {
? ? private final StringRedisTemplate stringRedisTemplate;
? ? public CacheClient(StringRedisTemplate stringRedisTemplate) {
? ? ? ? this.stringRedisTemplate = stringRedisTemplate;
? ? }
? ? //方法一
? ? public void set(String key, Object value, Long time, TimeUnit unit) {
? ? }
? ? //方法二
? ? public void setWithLogicExpire(String key, Object value, Long time, TimeUnit unit) {
? ? }
? ? //方法三
? ? public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Long time, TimeUnit unit, Function<ID, R> dbFallback) {
? ? }
? ? //方法四
? ? public <R, ID> R queryWithLogicalExpire(String prefix, ID id, String lockPre, Class<R> type,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Long time, TimeUnit unit, Function<ID, R> dbFallback) {
? ? }
? ? //線程池
? ? private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
? ? //獲取鎖
? ? private boolean tryLock(String key) {
? ? ? ? Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS);
? ? ? ? return BooleanUtil.isTrue(flag);
? ? }
? ? //釋放鎖
? ? private void unLock(String key) {
? ? ? ? stringRedisTemplate.delete(key);
? ? }
}
接下來我們可以不斷完善這些方法。
1.1 方法一
public void set(String key, Object value, Long time, TimeUnit unit) {
? ? stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
}
1.2 方法二
public void setWithLogicExpire(String key, Object value, Long time, TimeUnit unit) {
? ? RedisData redisData = new RedisData();
? ? redisData.setData(value);
? ? redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
? ? stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
}
1.3 方法三
public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type,
Long time, TimeUnit unit, Function<ID, R> dbFallback) {
String key = keyPrefix + id;
//1.從redis中查詢商鋪緩存
String json = stringRedisTemplate.opsForValue().get(key);
//2.判斷是否存在
if (StrUtil.isNotBlank(json)) {
//2.1.存在
return JSONUtil.toBean(json, type);
}
//2.2.不存在
//判斷是否為空值
if (json != null) {
//不為null,則必為空
return null;
}
//3.查詢數據庫
R r = dbFallback.apply(id);
if (r == null) {
//3.1.不存在,緩存空值
stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
} else {
//3.2.存在,緩存數據
this.set(key, r, time, unit);
}
return r;
}
方法三用到了函數式編程,這里非常巧妙,順便再貼一下調用方法是怎樣調用的:
Shop shop = cacheClient.queryWithPassThrough(CACHE_SHOP_KEY,id,Shop.class,CACHE_SHOP_TTL,TimeUnit.MINUTES,this::getById);
1.4 方法四
public <R, ID> R queryWithLogicalExpire(String prefix, ID id, String lockPre, Class<R> type,
Long time, TimeUnit unit, Function<ID, R> dbFallback) {
//1.從redis查詢商鋪緩存
String key = prefix + id;
String json = stringRedisTemplate.opsForValue().get(key);
//2.判斷是否存在
if (StrUtil.isBlank(json)) {
//未命中,直接返回空
return null;
}
//3.命中,判斷是否過期
RedisData redisData = JSONUtil.toBean(json, RedisData.class);
R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {
//3.1未過期,直接返回店鋪信息
return r;
}
//3.2.已過期,緩存重建
//3.3.獲取鎖
String lockKey = lockPre + id;
boolean flag = tryLock(lockKey);
if (flag) {
//3.4.獲取成功
//4再次檢查redis緩存是否過期,做double check
json = stringRedisTemplate.opsForValue().get(key);
//4.1.判斷是否存在
if (StrUtil.isBlank(json)) {
//未命中,直接返回空
return null;
}
//4.2.命中,判斷是否過期
redisData = JSONUtil.toBean(json, RedisData.class);
r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {
//4.3.未過期,直接返回店鋪信息
return r;
}
//4.4過期,返回舊數據
CACHE_REBUILD_EXECUTOR.submit(() -> {
//5.重建緩存
try {
R r1 = dbFallback.apply(id);
this.setWithLogicExpire(key, r1, time, unit);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
//釋放鎖
unLock(lockKey);
}
});
}
//7.獲取失敗,返回舊數據
return r;
}
2. 完整工具類代碼
@Component
@Slf4j
public class CacheClient {
? ? private final StringRedisTemplate stringRedisTemplate;
? ? public CacheClient(StringRedisTemplate stringRedisTemplate) {
? ? ? ? this.stringRedisTemplate = stringRedisTemplate;
? ? }
? ? public void set(String key, Object value, Long time, TimeUnit unit) {
? ? ? ? stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
? ? }
? ? public void setWithLogicExpire(String key, Object value, Long time, TimeUnit unit) {
? ? ? ? RedisData redisData = new RedisData();
? ? ? ? redisData.setData(value);
? ? ? ? redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
? ? ? ? stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
? ? }
? ? public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Long time, TimeUnit unit, Function<ID, R> dbFallback) {
? ? ? ? String key = keyPrefix + id;
? ? ? ? //1.從redis中查詢商鋪緩存
? ? ? ? String json = stringRedisTemplate.opsForValue().get(key);
? ? ? ? //2.判斷是否存在
? ? ? ? if (StrUtil.isNotBlank(json)) {
? ? ? ? ? ? //2.1.存在
? ? ? ? ? ? return JSONUtil.toBean(json, type);
? ? ? ? }
? ? ? ? //2.2.不存在
? ? ? ? //判斷是否為空值
? ? ? ? if (json != null) {
? ? ? ? ? ? //不為null,則必為空
? ? ? ? ? ? return null;
? ? ? ? }
? ? ? ? //3.查詢數據庫
? ? ? ? R r = dbFallback.apply(id);
? ? ? ? if (r == null) {
? ? ? ? ? ? //3.1.不存在,緩存空值
? ? ? ? ? ? stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
? ? ? ? } else {
? ? ? ? ? ? //3.2.存在,緩存數據
? ? ? ? ? ? this.set(key, r, time, unit);
? ? ? ? }
? ? ? ? return r;
? ? }
? ? public <R, ID> R queryWithLogicalExpire(String prefix, ID id, String lockPre, Class<R> type,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Long time, TimeUnit unit, Function<ID, R> dbFallback) {
? ? ? ? //1.從redis查詢商鋪緩存
? ? ? ? String key = prefix + id;
? ? ? ? String json = stringRedisTemplate.opsForValue().get(key);
? ? ? ? //2.判斷是否存在
? ? ? ? if (StrUtil.isBlank(json)) {
? ? ? ? ? ? //未命中,直接返回空
? ? ? ? ? ? return null;
? ? ? ? }
? ? ? ? //3.命中,判斷是否過期
? ? ? ? RedisData redisData = JSONUtil.toBean(json, RedisData.class);
? ? ? ? R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
? ? ? ? if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {
? ? ? ? ? ? //3.1未過期,直接返回店鋪信息
? ? ? ? ? ? return r;
? ? ? ? }
? ? ? ? //3.2.已過期,緩存重建
? ? ? ? //3.3.獲取鎖
? ? ? ? String lockKey = lockPre + id;
? ? ? ? boolean flag = tryLock(lockKey);
? ? ? ? if (flag) {
? ? ? ? ? ? //3.4.獲取成功
? ? ? ? ? ? //4再次檢查redis緩存是否過期,做double check
? ? ? ? ? ? json = stringRedisTemplate.opsForValue().get(key);
? ? ? ? ? ? //4.1.判斷是否存在
? ? ? ? ? ? if (StrUtil.isBlank(json)) {
? ? ? ? ? ? ? ? //未命中,直接返回空
? ? ? ? ? ? ? ? return null;
? ? ? ? ? ? }
? ? ? ? ? ? //4.2.命中,判斷是否過期
? ? ? ? ? ? redisData = JSONUtil.toBean(json, RedisData.class);
? ? ? ? ? ? r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
? ? ? ? ? ? if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {
? ? ? ? ? ? ? ? //4.3.未過期,直接返回店鋪信息
? ? ? ? ? ? ? ? return r;
? ? ? ? ? ? }
? ? ? ? ? ? //4.4過期,返回舊數據
? ? ? ? ? ? CACHE_REBUILD_EXECUTOR.submit(() -> {
? ? ? ? ? ? ? ? //5.重建緩存
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? R r1 = dbFallback.apply(id);
? ? ? ? ? ? ? ? ? ? this.setWithLogicExpire(key, r1, time, unit);
? ? ? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? ? ? ? throw new RuntimeException(e);
? ? ? ? ? ? ? ? } finally {
? ? ? ? ? ? ? ? ? ? //釋放鎖
? ? ? ? ? ? ? ? ? ? unLock(lockKey);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? });
? ? ? ? }
? ? ? ? //7.獲取失敗,返回舊數據
? ? ? ? return r;
? ? }
? ? private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
? ? //獲取鎖
? ? private boolean tryLock(String key) {
? ? ? ? Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS);
? ? ? ? return BooleanUtil.isTrue(flag);
? ? }
? ? //釋放鎖
? ? private void unLock(String key) {
? ? ? ? stringRedisTemplate.delete(key);
? ? }
}
原文鏈接:https://blog.csdn.net/Decade_Faiz/article/details/128665633
相關推薦
- 2022-05-01 pandas中關于apply+lambda的應用_python
- 2022-09-14 Android自定義視圖中圖片的處理_Android
- 2022-05-24 SQL?Server表空間碎片化回收的實現_MsSql
- 2022-06-25 C#將DataGridView中的數據保存到CSV和Excel中_C#教程
- 2022-11-03 C++11?寫一個只觸發一次槽函數的Qt?connect函數_C 語言
- 2022-05-18 Python繪制散點圖的教程詳解_python
- 2022-04-29 python?tkinter實現學生信息管理系統_python
- 2022-12-14 PostgreSQL模式匹配與正則表達式方法總結_PostgreSQL
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支