功能02-商铺查询缓存03

3.功能02-商铺查询缓存

3.6封装redis工具类

3.6.1需求说明

基于StringRedisTemplate封装一个工具列,满足下列需求:

方法1:将任意Java对象序列化为json,并存储在string类型的key中,并且可以设置TTL过期时间

方法2:将任意Java对象序列化为json,并存储在string类型的key中,并且可以设置逻辑过期时间,用户处理缓存击穿问题(针对热点key)

方法3:根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题

方法4:根据指定的key查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题(针对热点key)

3.6.2代码实现

(1)创建redis工具类,封装上述方法

package com.hmdp.utils;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component; import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function; import static com.hmdp.utils.RedisConstants.*; /**
* @author 李
* @version 1.0
* 封装redis工具类
*/
@Component
@Slf4j
public class CacheClient {
@Resource
private StringRedisTemplate stringRedisTemplate; /**
* 将任意Java对象序列化为json,并存储在string类型的key中,并且可以设置TTL过期时间
*
* @param key 缓存的key值
* @param value 缓存的value值
* @param time 过期时间值
* @param unit 过期的时间单位
*/
public void set(String key, Object value, Long time, TimeUnit unit) {
stringRedisTemplate.opsForValue()
.set(key, JSONUtil.toJsonStr(value), time, unit);
} /**
* 将任意Java对象序列化为json,并存储在string类型的key中,
* 并且可以设置逻辑过期时间,用户处理缓存击穿问题(针对热点key)
*
* @param key 缓存的key值
* @param value 缓存的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)));
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
} /**
* 根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题
*
* @param keyPrefix 查询的key值的前缀
* @param id 查询的key值的后缀
* @param type 要转换的Class类型
* @param dbFallback 传入的函数
* @param time 过期时间值
* @param unit 时间单位
* @param <R> 泛型
* @param <ID> 泛型
* @return 返回指定的类型对象
*/
public <R, ID> R queryWithPassThrough(
String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
String key = keyPrefix + id;
//redis查询缓存
String json = stringRedisTemplate.opsForValue().get(key);
//判断json是否存在
if (StrUtil.isNotBlank(json)) {
//存在,转为java对象并返回
return JSONUtil.toBean(json, type);
}
//判断是否为"",如果是,说明该key是为了解决缓存穿透设置的空值
if ("".equals(json)) {
//返回错误信息
return null;
}
//不存在,根据id查询数据库——使用函数式编程
R r = dbFallback.apply(id);
if (r == null) {//说明数据库中没有该数据
//缓存空值,应对缓存穿透
stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
//返回错误信息
return null;
}
//r存在,则将其写入redis
this.set(key, r, time, unit);
return r;
} private static final ExecutorService CACHE_REBUILD_EXECUTOR =
Executors.newFixedThreadPool(10); /**
* 根据指定的key查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题(针对热点key)
* @param keyPrefix 查询的key值的前缀
* @param id 查询的key值的后缀
* @param type 要转换的Class类型
* @param dbFallback 传入的函数
* @param time 过期时间值
* @param unit 时间单位
* @param <R> 泛型
* @param <ID> 泛型
* @return 返回指定的类型对象
*/
public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type,
Function<ID, R> dbFallback, Long time,
TimeUnit unit) {
String key = keyPrefix + id;
String json = stringRedisTemplate.opsForValue().get(key);
//这里不再考虑缓存穿透问题,因为key永不过期
if (StrUtil.isBlank(json)) {
//如果未命中,说明不是热点key,直接返回null
return null;
}
//如果命中
//先把json反序列化为对象
RedisData redisData = JSONUtil.toBean(json, RedisData.class);
R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
LocalDateTime expireTime = redisData.getExpireTime();
//判断是否逻辑过期
if (expireTime.isAfter(LocalDateTime.now())) {
//未过期,直接返回信息
return r;
}
//过期,获取互斥锁
String lockKey = LOCK_SHOP_KEY + id;
boolean isLock = tryLock(lockKey);
if (isLock) {//成功获取互斥锁
//开启独立线程
CACHE_REBUILD_EXECUTOR.submit(() -> {
try {
//重建缓存
//先查询数据库
R apply = dbFallback.apply(id);
//再存入reids缓存
this.setWithLogicalExpire(key, apply, time, unit);
} catch (Exception e) {
throw new RuntimeException(e);
}
//释放互斥锁
unLock(lockKey);
});
}
//如果未获取互斥锁,直接返回旧数据
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);
}
}

(2)修改ShopServiceImpl,调用封装好的方法,简化代码

package com.hmdp.service.impl;

import ...

/**
* 服务实现类
*
* @author 李
* @version 1.0
*/
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop>
implements IShopService {
@Resource
StringRedisTemplate stringRedisTemplate; @Resource
private CacheClient cacheClient; @Override
public Result queryById(Long id) {
//缓存穿透
//Shop shop =
// cacheClient.queryWithPassThrough
// (CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES); //缓存击穿方案(逻辑过期)
Shop shop = cacheClient.queryWithLogicalExpire
(CACHE_SHOP_KEY, id, Shop.class, this::getById, 20L, TimeUnit.MINUTES); if (shop == null) {
return Result.fail("店铺不存在!");
}
return Result.ok(shop);
} @Override
@Transactional
public Result update(Shop shop) {
...
}
}

3.7缓存总结

day04-商家查询缓存03的更多相关文章

  1. mysql查询缓存打开、设置、参数查询、性能变量意思

    http://blog.sina.com.cn/s/blog_75ad10100101by7j.html http://www.cnblogs.com/zemliu/archive/2013/08/0 ...

  2. ECMall关于数据查询缓存的问题

    刚接触Ecmall的二次开发不久,接到一个任务.很常见的任务,主要是对数据库进行一些操作,其中查询的方法我写成这样: 01 function get_order_data($goods_id) 02 ...

  3. 让EFCore更疯狂些的扩展类库(二):查询缓存、分部sql、表名替换的策略配置

    前言 上一篇介绍了扩展类库的功能简介,通过json文件配置sql语句 和 sql语句的直接执行,这篇开始说明sql配置的策略模块:策略管理器与各种策略的配置. 类库源码:github:https:// ...

  4. 面试题:你有没有搞混查询缓存和Buffer Pool

    一. 关注送书!<Netty实战> 文章公号号首发!连载中!关注微信公号回复:"抽奖" 可参加抽活动 首发地址:点击跳转阅读原文,有更好的阅读体验 使用推荐阅读,有更好 ...

  5. hibernate笔记--缓存机制之 二级缓存(sessionFactory)和查询缓存

    二级缓存(sessionFactory): Hibernate的二级缓存由SessionFactory对象管理,是应用级别的缓存.它可以缓存整个应用的持久化对象,所以又称为“SessionFactor ...

  6. mybatis入门基础(八)-----查询缓存

    一.什么是查询缓存 mybatis提供查询缓存,用于减轻数据压力,提高数据库性能. mybaits提供一级缓存,和二级缓存. 1.1. 一级缓存是sqlSession级别的缓存.在操作数据库时需要构造 ...

  7. mybatis中的查询缓存

    一: 查询缓存 Mybatis提供查询缓存,用于减轻数据压力,提高数据库压力. Mybatis提供一级缓存和二级缓存. 在操作数据库时需要构造SqlSession对象,在对象中有一个数据结构(Hash ...

  8. 11g新特性-查询缓存(1)

    众所周知,访问内存比访问硬盘快得多,除非硬盘体系发生革命性的改变.可以说缓存在Oracle里面无处不在,结果集缓存(Result Cache)是Oracle Database 11g新引入的功能,引入 ...

  9. 【Mybatis框架】查询缓存(一级缓存)

    做Java的各位程序员们,估计SSH和SSM是我们的基础必备框架.也就是说我们都已经至少接触过了这两套常见的集成框架.当我们用SSH的时候,相信很多人都接触过hibernate的两级缓存,同样,相对应 ...

  10. MySQL查询缓存

    MySQL查询缓存 用于保存MySQL查询语句返回的完整结果,被命中时,MySQL会立即返回结果,省去解析.优化和执行等阶段. 如何检查缓存? MySQL保存结果于缓存中: 把SELECT语句本身做h ...

随机推荐

  1. org.nutz.http.Http忽略https SSL证书验证

    访问的是一个https get请求,报错需要SSL证书验证,以下方法直接跳过 boolean check = Http.disableJvmHttpsCheck(); // 忽略https的证书检查

  2. npm安装时加 --save和不加的区别

    npm install xxx --save 命令是安装模块到项目node_modules目录下,会将模块依赖写入package.json文件中的dependencies{}下.如果将node_mod ...

  3. vs2013安装完VASSISTX助手之后字体变成斜体如何解决?

    VC助手为最新版本. 1. 打开vc助手选项 2. 取消勾选"show stable symbols in italics"

  4. 读取nrf52832的ADC,并且获取N个数组中的中间值

    //****读取中间值****// short GetMedianNum(short * bArray, short iFilterLen) { short i,j,bTemp;// 排序循环 for ...

  5. [扫描工具]dirsearch简单使用

    [扫描工具]dirsearch简单使用 dirsearch是一个python开发的目录扫描工具.和我们平时使用的dirb.御剑之类的工具一样,就是为了扫描网站的敏感文件和目录从而找到突破口. 安装: ...

  6. NET 实现 Cron 定时任务执行,告别第三方组件

    原文连接: (96条消息) NET 实现 Cron 定时任务执行,告别第三方组件_.net 定时任务_Phil Arist的博客-CSDN博客 代码: using System.Globalizati ...

  7. 后端006_登录之后返回Token

    现在开始我们就可以写登录相关的东西了.首先登录相关的流程是这样的,前端输入用户和密码传给后端,后端判断用户名和密码是否正确,若正确,则生成JWT令牌,若不正确,则需要让前端重新输入,前端如果拿到了JW ...

  8. 在centos上使用nginx-rtmp实现hls(Http live Streaming)直播

    欢迎访问我的个人博客:xie-kang.com 原文地址 以下步骤建议在同一目录(如:~/Downloads)下执行(防止小白把目录弄混乱) 下载并解压nginx-rtmp(建议在~/Download ...

  9. 关于使用antd-proTable,报错 ResizeObserver loop limit exceeded

    错误如上,原因有几种情况 一:columns中,属性又ellipsis属性,但是没有设置width,导致table不知道如何计算在什么时候,开始对内容进行加省略号,出现了计算错误 如  const c ...

  10. windows10环境下的RabbitMQ安装步骤(图文)(转载)

    第一步:下载并安装erlang 原因:RabbitMQ服务端代码是使用并发式语言Erlang编写的,安装Rabbit MQ的前提是安装Erlang. 下载地址:http://www.erlang.or ...