当前位置: 首页 > news >正文

黑马点评学习笔记07(缓存工具封装)

前言

用Redis完成登录界面后,我们实现了点评项目的店铺查询功能为并基于Redia解决了缓存穿透,缓存雪崩,缓存击穿等一系列Java并发问题,可以发现我们的代码会不会太长太麻烦了,每一个查询功能都要有这么长的代码真的很麻烦,那我们可以封装起来,用的时候直接调用。

🤪先来看看要封装的方法是什么?

在这里插入图片描述
🙌🙌🙌方法1与方法3配合起来,负责常见的普通的缓存,用于解决缓存穿透;方法2与方法4结合,负责热点缓存,用于解决缓存击穿

了解封装的方法,有利于我们读懂Java底层的源代码,其实逻辑都是一样的,只是传参,其中用到了Java泛型

来看看什么是 Java 泛型🙌🙌🙌🙌🙌🙌?

✅ 定义
泛型(Generics) 是 Java 5 引入的一种机制,允许在定义类、接口或方法时使用类型参数(Type Parameter),从而在编译期提供类型安全检查,避免运行时类型转换错误。

✅ 基本语法
泛型类:class Box { … }
泛型方法: T get(T item) { … }
通配符:List<?>、List<? extends Number>、List<? super Integer>

来看看封装好的工具类的方法:

package com.hmdp.utils;import...
@Slf4j
@Component
public class CacheClient {private final StringRedisTemplate stringRedisTemplate ;public CacheClient(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}/*** 将数据加入Redis,并设置有效期** @param key* @param value* @param timeout* @param unit*/public void set(String key, Object value, Long timeout, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), timeout, unit);}/*** 设置缓存和逻辑过期时间** @param key  key前缀* @param value* @param time  有效期* @param unit  有效期的时间单位*/public void setWithLogicalExpire(String key, Object value, Long time,  TimeUnit unit) {// 设置逻辑过期RedisData redisData = new RedisData();redisData.setData(value);redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));// 写入RedisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));}/*** 根据id从缓存中查找数据(缓存参透)* @param keyPrefix Key前缀* @param id* @param type  返回数据类型* @param dbFallback 数据库查询方法* @param time  有效期* @param unit  有效期的时间单位* @return* @param <R>* @param <ID>*/public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time,  TimeUnit unit) {String key = keyPrefix + id;//1.从Redis中查询缓存String json = stringRedisTemplate.opsForValue().get(key);//2.存在,直接返回if (StrUtil.isNotBlank(json)) {return JSONUtil.toBean(json, type);}//判断是否是空值if (json == null) {//返回错误return null;}//3.不存在,根据id查询数据库R r = dbFallback.apply(id);//4.数据库不存在,返回错误if (r == null) {//将空值写入RedisstringRedisTemplate.opsForValue().set(key, "",CACHE_NULL_TTL , TimeUnit.MINUTES);return null;}//5.存在,写入Redisthis.set(key, r, time, unit);//6.返回return r;}/*创建线程池*/private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);/*缓存重建(逻辑过期)*/public <R, ID> R queryWithLogicalExpire( String keyPrefix,ID id, Class<R> type, Function<ID, R> dbFallback, Long time,  TimeUnit unit) {String key = keyPrefix + id;//1.从Redis中查询缓存String json = stringRedisTemplate.opsForValue().get(key);//2.判断是否存在if (StrUtil.isBlank(json)) {//3.存在,直接返回return null;}//4.命中,需要先将json反序列化为对象RedisData redisData = JSONUtil.toBean(json, RedisData.class);JSONObject data =  (JSONObject) redisData.getData();R r = JSONUtil.toBean(data, type);LocalDateTime expireTime = redisData.getExpireTime();//5.判断是否过期if (expireTime.isAfter(LocalDateTime.now())) {//5.1 未过期,直接返回return r;}//5.2 处于过期时间,需要缓存重建//6.缓存重建//6.1 获取互斥锁String lock = LOCK_SHOP_KEY + id;boolean isLock = tryLock(lock);//6.2 判断是否获取成功if (isLock) {//6.3 成功,开启独立线程,实现缓存重建CACHE_REBUILD_EXECUTOR.submit(() -> {try {//重建缓存//1.查询数据库R r1 = dbFallback.apply(id);//2.写入Redisthis.setWithLogicalExpire(key, r1, time , unit);} catch (Exception e) {throw new RuntimeException(e);} finally {//释放锁unLock(lock);}});}//6.3 成功,返回结果//6.4 失败,返回错误//7.返回return r;}/*尝试获取锁*/private boolean tryLock(String key) {Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}/*释放锁*/private void unLock(String key) {stringRedisTemplate.delete(key);}}
    /*** 根据id查询商铺数据** @param id* @return*/@Overridepublic Result queryById(Long id) {// 调用解决缓存穿透的方法
//        Shop shop = cacheClient.queryWithPassThrough(CACHE_SHOP_KEY, id, Shop.class,
//                this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);
//        if (Objects.isNull(shop)){
//            return Result.fail("店铺不存在");
//        }// 调用解决缓存击穿的方法Shop shop = cacheClient.queryWithLogicalExpire(CACHE_SHOP_KEY, id, Shop.class,this::getById, CACHE_SHOP_TTL, TimeUnit.SECONDS);if (Objects.isNull(shop)) {return Result.fail("店铺不存在");}return Result.ok(shop);}

在 CacheClient 类中,泛型主要出现在两个核心方法中:

public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time,  TimeUnit unit
)public <R, ID> R queryWithLogicalExpire(String keyPrefix,ID id, Class<R> type, Function<ID, R> dbFallback, Long time,  TimeUnit unit
)

1. <R, ID> 是什么?
这是方法级别的泛型声明,表示:

R:返回值的类型(Result),比如 Shop、User 等业务实体。
ID:主键的类型(Identifier),比如 Long、String、Integer。

2. 参数 Class type 的作用

  • 用于 反序列化时指定目标类型

  • 因为 Java 的泛型存在 类型擦除(Type Erasure),运行时无法直接知道 R 是什么类型。

  • 所以需要显式传入 Class(如 Shop.class)来告诉 JSONUtil.toBean():请把 JSON 转成 Shop 对象。

✅ ShopService类中的示例调用:

// 调用解决缓存击穿的方法Shop shop = cacheClient.queryWithLogicalExpire(CACHE_SHOP_KEY, id, Shop.class,this::getById, CACHE_SHOP_TTL, TimeUnit.SECONDS);

3. 参数 Function<ID, R> dbFallback 的含义

  • 这是一个 函数式接口(Java 8+),表示“根据 ID 查询数据库”的逻辑。
  • Function<ID, R>:输入 ID,输出 R。
  • 使用 Lambda 表达式传入数据库查询逻辑,解耦缓存逻辑与具体业务。

本文是学习黑马程序员—黑马点评项目的课程笔记,小白啊!!!写的不好轻喷啊🤯如果觉得写的不好,点个赞吧🤪(批评是我写作的动力)

…。。。。。。。。。。。…请添加图片描述

…。。。。。。。。。。。…

http://www.dtcms.com/a/574490.html

相关文章:

  • BLDC电流采样的四种方式
  • 物流行业网站建设市场分析品牌策划方案案例
  • 高校对网站建设的重视郑州建设电商网站
  • 网站后台管理代码凡科h5在线制作
  • 做网站外包多少钱网站建设 工作计划
  • 自己做的网站很卡深圳建立网站公司
  • Trae 大模型选型对比
  • IO多路复用之epoll
  • 模拟一个机械手指:从数学模型到高保真仿真的全平台指南
  • 响应式网站导航栏内容矿区网站建设
  • 网站建设哪家最好wordpress安装到跟目录
  • FFNN(前馈神经网络)层
  • 建设隔离变压器移动网站营销课程培训视频
  • 安阳企业建网站建设用地规划查询网站
  • 【Kernel】Linux CFS(完全公平调度器)实现原理与机制
  • Alibaba Cloud Linux 3 安装 Tomcat
  • 什么自查询?为什么在 RAG 中需要自查询?
  • 做早餐煲汤网站wordpress移动端顶部导航栏
  • 地坪漆东莞网站建设技术支持做淘宝的人就跟做网站一样
  • Vue 用户管理系统(路由相关练习二)
  • 三维建筑非法入侵情景推演
  • 新加坡建设局网站企业网站策划过程
  • 工程师报考网站企业站系统
  • Agent 设计与上下文工程- 03 Workflow 设计模式(下)
  • 硅基计划6.0 JavaEE 壹 多线程及核心内容
  • 网站建设+三乐seo优化系统哪家好
  • 游戏网站建设收费明细自己怎么制作假山
  • 调用模型的两个参数 temperature 和 max_new_tokens 指什么
  • Deepseek在它擅长的AI数据处理领域还有是有低级错误【k折交叉验证中每折样本数计算】
  • 影刀RPA实时监控抖店DSR评分,AI预警异常波动,店铺权重稳如泰山![特殊字符]