课程介绍
heima 点评Redis —— 项目
1️⃣ 短信登录 —— Redis 的共享 session 应用
2️⃣ 商户查询缓存 —— 企业的缓存使用技巧| 缓存雪崩、穿透等问题解决
3️⃣ 达人探店 —— 基于 List 点赞链表|基于 SortedSet 的点赞排行榜
4️⃣ 优惠券秒杀 —— Redis 的计数器| Lua 脚本 Redis | 分布式锁 | Redis 的三种消息队列 ⭐
5️⃣ 好友关注 —— 基于 Set 集合的关注|取关|共同关注|消息推送的功能
6️⃣ 附近的商户 —— Redis 的 GeoHash 的应用
7️⃣ 用户签到 —— Redis 的 BitMap 数据统计功能
8️⃣ UV 统计 —— Redis 的 HyperLogLog 的统计功能
缓存就是数据交换的缓冲区(称之为 Cache),是存储数据的临时地方,一般读写性能较高。
缓存的作用
1️⃣ 降低后端负载
2️⃣ 提高读写效率,降低响应时间
缓存的成本
1️⃣ 数据的一致性成本问题
2️⃣ 代码维护成本
3️⃣ 运维成本
查询商户的业务流程
根据请求查询对应的数据
@RestController
@RequestMapping("/shop")
public class ShopController {
@Resource
public IShopService shopService;
/**
* 根据id查询商铺信息
* @param id 商铺id
* @return 商铺详情数据
*/
@GetMapping("/{id}")
public Result queryShopById(@PathVariable("id") Long id) {
return Result.ok(shopService.getById(id));
}
}
缓存作用模型
添加缓存的业务逻辑思路
1️⃣ 当用户进行提交查询商铺时的请求时 会携带商铺的 id 信息
2️⃣ 尝试 通过 商铺 id 信息 到缓存中查看是否存在对应缓存信息
3️⃣ 结束
这里小付采用 Redis Hash 将数据存入 redis中
【参考资料】 【运行报错】使用BeanUtil做beanToMap时,转换字段(可能为null)的属性为String类型
1️⃣ 报错信息
StringRedisTemplateredis
需要转换成的map的各个字段都是String类型!而bean的各个字段类型各不相同Map<String, Object> map = BeanUtil.beanToMap(shop,new HashMap<>(),
CopyOptions.create().
setIgnoreNullValue(true)
.setFieldValueEditor((fieldName,fieldValue) -> fieldValue.toString()));
stringRedisTemplate.opsForHash().putAll(RedisConstants.CACHE_SHOP_KEY + id,map);
java.lang.NullPointerException: null
at com.hmdp.service.impl.ShopServiceImpl.lambda$queryById$0(ShopServiceImpl.java:81) ~[classes/:na]
at cn.hutool.core.bean.copier.CopyOptions.editFieldValue(CopyOptions.java:258) ~[hutool-all-5.7.17.jar:na]
at cn.hutool.core.bean.copier.BeanCopier.lambda$beanToMap$1(BeanCopier.java:233) ~[hutool-all-5.7.17.jar:na]
at java.util.LinkedHashMap$LinkedValues.forEach(LinkedHashMap.java:608) ~[na:1.8.0_181]
at cn.hutool.core.bean.BeanUtil.descForEach(BeanUtil.java:182) ~[hutool-all-5.7.17.jar:na]
at cn.hutool.core.bean.copier.BeanCopier.beanToMap(BeanCopier.java:195) ~[hutool-all-5.7.17.jar:na]
at cn.hutool.core.bean.copier.BeanCopier.copy(BeanCopier.java:106) ~[hutool-all-5.7.17.jar:na]
at cn.hutool.core.bean.BeanUtil.beanToMap(BeanUtil.java:690) ~[hutool-all-5.7.17.jar:na]
2️⃣ 原因分析
Map<String, Object> map = BeanUtil.beanToMap(shop,new HashMap<>(),
CopyOptions.create().
setIgnoreNullValue(true)
.setFieldValueEditor((fieldName,fieldValue) -> fieldValue + ""));
stringRedisTemplate.opsForHash().putAll(RedisConstants.CACHE_SHOP_KEY + id,map);
但还是存在问题:
现在空值是以字符串 “null” 存在redis中的
当再次点击此商铺,此时缓存命中,故将redis中的hash类型数据转为 shop 对象时,“null” 又无法将string类型的value解析成一个数字类型
此时注意到,我代码中明明将 空值忽略掉了
setIgnoreNullValue(true)
但debug却发现setIgnoreNullValue(true)
并没有 生效
3️⃣ 解决方法
// todo 3.2 判断商铺缓存是否存在 => 存在 将商铺数据写入到 Redis 中 => 返回 商铺信息
Map<String, Object> redisCache = BeanUtil.beanToMap(shop,new HashMap<>()
,CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((k,v)->{
if (k == null){
v = "0";
}else {
v = v + "";
}
return v;
}));
stringRedisTemplate.opsForHash().putAll(shopKey , redisCache);
return Result.ok(redisCache);
步骤一:改写 Service 层业务逻辑
ShopController
/**
* 根据id查询商铺信息
* @param id 商铺id
* @return 商铺详情数据
*/
@GetMapping("/{id}")
public Result queryShopById(@PathVariable("id") Long id) {
return shopService.queryById(id);
}
IShopService
public interface IShopService extends IService<Shop> {
Result queryById(Long id);
}
ShopServiceImpl
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
@Autowired
StringRedisTemplate stringRedisTemplate ;
@Override
public Result queryById(Long id) {
// todo 1. 根据商铺 id 到 Redis 中查询商铺缓存
String shopKey = RedisConstants.CACHE_SHOP_KEY + id ;
Map<Object, Object> shopMap = stringRedisTemplate.opsForHash().entries(shopKey);
// todo 2. 缓存命中 => 返回商铺信息
if (shopMap.isEmpty()){
// todo 3. 缓存未命中 => 根据 id 查询数据库
Shop shop = getById(id);
// todo 3.1 判断商铺缓存是否存在 => 不存在 返回404状态码 提示错误信息
if (shop == null){
return Result.fail("404 您访问的商户不存在!");
}
// todo 3.2 判断商铺缓存是否存在 => 存在 将商铺数据写入到 Redis 中 => 返回 商铺信息
Map<String, Object> redisCache = BeanUtil.beanToMap(shop,new HashMap<>()
,CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((k,v)->{
if (k == null){
v = "0";
}else {
v = v + "";
}
return v;
}));
stringRedisTemplate.opsForHash().putAll(shopKey , redisCache);
return Result.ok(redisCache);
}
// todo 4 结束
return Result.ok(shopMap);
}
}
步骤二:启动应用程序进行业务逻辑测试
1️⃣ 第一次查询还是走了数据库的查询 所以 时间为 153 ms
查看对应 Redis 中是否已经缓存好了对应的数据
此时已经缓存到了数据库
此时我们再次去进行查询之前的数据查看请求时间
此时就发现我们之前缓存好的数据只需要 16 ms 就可以完成请求了 快了 将近 10倍
反观IDEA控制台中并没有对应的 SQL 查询日志,故我们的缓存生效 极大提高了效率。
步骤一:这里只提供对应的Controller 层代码的编写对应的业务逻辑要求
/**
* 功能描述
* 商业缓存的具体实现 以 String 类型传入
* @date 2022/7/10
* @author Alascanfu
*/
@Override
public Result cacheStringQueryById(Long id) {
// todo 1. 根据商铺 id 到 Redis 中查询商铺缓存
String shopKey = RedisConstants.CACHE_SHOP_KEY + id ;
// todo 2. 缓存命中 => 返回商铺信息
String shopJson = stringRedisTemplate.opsForValue().get(shopKey);
if (StrUtil.isNotBlank(shopJson)){
Shop shop = JSONUtil.toBean(shopJson , Shop.class);
return Result.ok(shop) ;
}
// todo 3. 缓存未命中 => 根据 id 查询数据库
Shop shop = getById(id);
if (shop == null){
// todo 3.1 判断商铺缓存是否存在 => 不存在 返回404状态码 提示错误信息
return Result.fail("404 您访问的商户不存在!");
}
// todo 3.2 判断商铺缓存是否存在 => 存在 将商铺数据写入到 Redis 中 => 返回 商铺信息
stringRedisTemplate.opsForValue().set(shopKey,JSONUtil.toJsonStr(shopJson));
return Result.ok(shop);
}