day02-2-商铺查询缓存
功能02-商铺查询缓存
3.商铺详情缓存查询
3.1什么是缓存?
缓存就是数据交换的缓冲区(称作Cache),是存储数据的临时地方,一般读写性能较高。
缓存的作用:
- 降低后端负载
- 提高读写效率,降低响应时间
缓存的成本:
- 数据一致性成本
- 代码维护成本
- 运维成本
3.2需求说明
如下,当我们点击商店详情的时候,前端会向后端发出请求,后端需要把相关的商店数据返回给客户端显示。


3.3思路分析(添加Redis缓存)
使用Redis的缓存模型如下:
当客户端发送请求到服务端时,先去redis中查询有没有对应的数据:
- 如果命中,则直接给客户端返回数据,这样直接访问数据库的请求就会大大减少
- 如果未命中,则到数据库中查询,同时将数据写入redis,防止下一次查询同样的数据,然后将数据返回给客户端

3.4代码实现
(1)Shop.java 实体类
package com.hmdp.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @author 李
* @version 1.0
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("tb_shop")
public class Shop implements Serializable {
private static final long serialVersionUID = 1L;
//主键
@TableId(value = "id", type = IdType.AUTO)
private Long id;
//商铺名称
private String name;
//商铺类型id
private Long typeId;
//商铺图片,多个图片以','隔开
private String images;
//商圈,例如陆家嘴
private String area;
//地址
private String address;
//经度
private Double x;
//纬度
private Double y;
//均价,取整数
private Long avgPrice;
//销量
private Integer sold;
//评论数量
private Integer comments;
//评分,1~5分,乘10保存,避免小数
private Integer score;
//营业时间,例如 10:00-22:00
private String openHours;
//创建时间
private LocalDateTime createTime;
//更新时间
private LocalDateTime updateTime;
@TableField(exist = false)
private Double distance;
}
(2)对应的mapper接口
package com.hmdp.mapper;
import com.hmdp.entity.Shop;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* Mapper 接口
*
* @author 李
* @version 1.0
*/
public interface ShopMapper extends BaseMapper<Shop> {
}
(3)IShopService.java 接口
package com.hmdp.service;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* 服务类
*
* @author 李
* @version 1.0
*/
public interface IShopService extends IService<Shop> {
Result queryById(Long id);
}
(4)ShopServiceImpl 服务实现类
package com.hmdp.service.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import static com.hmdp.utils.RedisConstants.*;
/**
*
* 服务实现类
*
* @author 李
* @version 1.0
*/
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {
@Resource
StringRedisTemplate stringRedisTemplate;
@Override
public Result queryById(Long id) {
String key = CACHE_SHOP_KEY + id;
//1.从redis中查询商铺缓存
String shopJson = stringRedisTemplate.opsForValue().get(key);
//2.判断缓存是否命中
if (StrUtil.isNotBlank(shopJson)) {
//2.1若命中,直接返回商铺信息
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
return Result.ok(shop);
}
//2.2未命中,根据id查询数据库,判断商铺是否存在数据库中
Shop shop = getById(id);
if (shop == null) {
//2.2.1不存在,则返回404
return Result.fail("店铺不存在!");
}
//2.2.2存在,则将商铺数据写入redis中
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));
return Result.ok(shop);
}
}
(5)ShopController 控制类
package com.hmdp.controller;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.service.IShopService;
import com.hmdp.utils.SystemConstants;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* 前端控制器
*
* @author 李
* @version 1.0
*/
@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 shopService.queryById(id);
}
}
(6)测试:首次查询的时候因为数据为写入reids,因此查询较慢,第二次因为已写入redis,查询较快


4.商铺类型缓存查询
4.1需求说明
店铺类型在首页和其他多个页面都会用到,如下:

要求当我们点击商铺类型的时候,前端会向后端发出请求,后端需要把相关的商店类型数据返回给客户端显示:


4.2思路分析
该功能的实现思路与上述的思路大体一致。
4.3代码实现
(1)实体类 ShopType
package com.hmdp.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @author 李
* @version 1.0
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("tb_shop_type")
public class ShopType implements Serializable {
private static final long serialVersionUID = 1L;
//主键
@TableId(value = "id", type = IdType.AUTO)
private Long id;
//类型名称
private String name;
//图标
private String icon;
//顺序
private Integer sort;
//创建时间
@JsonIgnore
private LocalDateTime createTime;
//更新时间
@JsonIgnore
private LocalDateTime updateTime;
}
(2)ShopTypeMapper接口
package com.hmdp.mapper;
import com.hmdp.entity.ShopType;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* Mapper 接口
*
* @author 李
* @version 1.0
*/
public interface ShopTypeMapper extends BaseMapper<ShopType> {
}
(3)服务类接口 IShopTypeService
package com.hmdp.service;
import com.hmdp.dto.Result;
import com.hmdp.entity.ShopType;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* 服务类接口
*
* @author 李
* @version 1.0
*/
public interface IShopTypeService extends IService<ShopType> {
Result queryShopList();
}
(4)服务实现类 ShopTypeServiceImpl
package com.hmdp.service.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.ShopType;
import com.hmdp.mapper.ShopTypeMapper;
import com.hmdp.service.IShopTypeService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import static com.hmdp.utils.RedisConstants.CACHE_SHOP_TYPE;
/**
* 服务实现类
*
* @author 李
* @version 1.0
*/
@Service
public class ShopTypeServiceImpl extends ServiceImpl<ShopTypeMapper, ShopType> implements IShopTypeService {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result queryShopList() {
//查询redis中有没有店铺类型缓存
String shopTypeJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_TYPE);
//如果有,则将其转为对象类型,并返回给客户端
if (StrUtil.isBlank(shopTypeJson)) {
List<ShopType> shopTypeList = JSONUtil.toList(shopTypeJson, ShopType.class);
return Result.ok(shopTypeList);
}
//如果redis中没有缓存,到DB中查询
//如果DB中没有查到,返回错误信息
List<ShopType> list = query().orderByAsc("sort").list();
if (list == null) {
return Result.fail("查询不到店铺类型!");
}
//如果DB查到了数据
//将数据存入Redis中(转为json类型存入)
stringRedisTemplate.opsForValue()
.set(CACHE_SHOP_TYPE, JSONUtil.toJsonStr(list));
//并返回给客户端
return Result.ok(list);
}
}
(5)控制类 ShopTypeController
package com.hmdp.controller;
import com.hmdp.dto.Result;
import com.hmdp.entity.ShopType;
import com.hmdp.service.IShopTypeService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
* 前端控制器
*
* @author 李
* @version 1.0
*/
@RestController
@RequestMapping("/shop-type")
public class ShopTypeController {
@Resource
private IShopTypeService typeService;
@GetMapping("list")
public Result queryTypeList() {
return typeService.queryShopList();
}
}
(6)测试,访问客户端首页,
返回的数据如下:


5.缓存更新
5.1缓存更新策略

5.1.1主动更新策略
- Cache Aside Pattern:由缓存的调用者,在更新数据库的同时更新缓存(可控性最高,推荐使用)
- Read/Write Through Pattern:缓存与数据库整合为一个服务,由服务来维护一致性。调用者调用该服务,无需关心缓存一致性问题
- Write Behind Caching Pattern:调用者只操作缓存,由其他线程异步的将缓存数据持久化到数据库,保证最终一致
操作缓存和数据库时有三个问题需要考虑:
删除缓存还是更新缓存?
- 更新缓存:每次更新数据库都更新缓存,无效写操作较多
- 删除缓存:更新数据库时让缓存失效,查询时再更新缓存(推荐使用)
如何保证缓存与数据库的操作的同时成功或失败?(原子性)
- 单体系统,将缓存与数据库操作放在一个事务
- 分布式系统,利用TCC等分布式事务方案
先操作缓存还是先操作数据库?(线程安全问题)

如上,虽然两种方案都有可能造成缓存和数据库不一致,但更推荐先更新数据库再删除缓存。
先更新数据库再删除缓存出现数据不一致概率更低,因为操作缓存一般比数据库更快,所以发生右图的情况很低(右图)。即使发生了,可以配合TTL定时清除缓存。
5.1.2总结
缓存更新策略的最佳实践方案:
- 低一致性需求:使用Redis自带的内存淘汰机制即可
- 高一致性需求:主动更新,并以超时剔除作为兜底方案
- 读操作:
- 缓存命中则直接返回
- 缓存未命中则查询数据库,并写入缓存,设定超时时间
- 写操作:
- 先写数据库,然后再删除缓存
- 要确保数据库与缓存操作的原子性
- 读操作:
5.2需求说明
给查询商铺的缓存添加超时剔除和主动更新策略:
- 根据id查询店铺时,如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置超时时间
- 根据id修改店铺,先修改数据库,再删除缓存
5.3代码实现
(1)修改ShopServiceImpl的queryById()方法,设置超时时间

并添加update()方法如下:
@Override
@Transactional
public Result update(Shop shop) {
Long id = shop.getId();
if (id == null) {
return Result.fail("店铺id不能为空");
}
//1.更新数据库
updateById(shop);
//2.删除redis缓存
stringRedisTemplate.delete(CACHE_SHOP_KEY + id);
return Result.ok();
}
(2)修改IShopService,添加方法声明
Result update(Shop shop);
(3)修改ShopController,添加方法
/**
* 更新商铺信息
* @param shop 商铺数据
* @return 无
*/
@PutMapping
public Result updateShop(@RequestBody Shop shop) {
// 写入数据库
return shopService.update(shop);
}
(4)测试
读操作:首次访问店铺详情,可以看到redis中存入数据,并且设置了TTL

写操作:使用postman向服务端发送更新店铺信息请求,可以看到当更新数据时候,先更新数据库,然后将redis的缓存删除。之后如果再有查询,将会重建redis的缓存,实现数据的一致性。
day02-2-商铺查询缓存的更多相关文章
- hibernate笔记--缓存机制之 二级缓存(sessionFactory)和查询缓存
二级缓存(sessionFactory): Hibernate的二级缓存由SessionFactory对象管理,是应用级别的缓存.它可以缓存整个应用的持久化对象,所以又称为“SessionFactor ...
- mybatis入门基础(八)-----查询缓存
一.什么是查询缓存 mybatis提供查询缓存,用于减轻数据压力,提高数据库性能. mybaits提供一级缓存,和二级缓存. 1.1. 一级缓存是sqlSession级别的缓存.在操作数据库时需要构造 ...
- mybatis中的查询缓存
一: 查询缓存 Mybatis提供查询缓存,用于减轻数据压力,提高数据库压力. Mybatis提供一级缓存和二级缓存. 在操作数据库时需要构造SqlSession对象,在对象中有一个数据结构(Hash ...
- 11g新特性-查询缓存(1)
众所周知,访问内存比访问硬盘快得多,除非硬盘体系发生革命性的改变.可以说缓存在Oracle里面无处不在,结果集缓存(Result Cache)是Oracle Database 11g新引入的功能,引入 ...
- 【Mybatis框架】查询缓存(一级缓存)
做Java的各位程序员们,估计SSH和SSM是我们的基础必备框架.也就是说我们都已经至少接触过了这两套常见的集成框架.当我们用SSH的时候,相信很多人都接触过hibernate的两级缓存,同样,相对应 ...
- MySQL查询缓存
MySQL查询缓存 用于保存MySQL查询语句返回的完整结果,被命中时,MySQL会立即返回结果,省去解析.优化和执行等阶段. 如何检查缓存? MySQL保存结果于缓存中: 把SELECT语句本身做h ...
- mysql查询缓存参数
由人说mysql查询缓存是鸡肋,也许吧,但还是要看场景: 查询缓存: 开启查询缓存:/etc/my.cnfquery_cache_type=1 重启
- [原创]java WEB学习笔记93:Hibernate学习之路---Hibernate 缓存介绍,缓存级别,使用二级缓存的情况,二级缓存的架构集合缓存,二级缓存的并发策略,实现步骤,集合缓存,查询缓存,时间戳缓存
本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...
- Hibernate <查询缓存>
查询缓存: 定义:查询缓存它是基于二级缓存的,可以保存普通属性查询的结果,查询对象实体时,他会保存id作为键,查询结果作为值,下个对象访问时,可以直接查到 查询缓存查询实体对象时,显著的特点是,会执行 ...
- 对MySql查询缓存及SQL Server过程缓存的理解及总结
一.MySql的Query Cache 1.Query Cache MySQL Query Cache是用来缓存我们所执行的SELECT语句以及该语句的结果集.MySql在实现Query Cache的 ...
随机推荐
- Q:su命令切换用户无法使用,被拒绝
su命令切换用户无法使用,被拒绝 问题描述 su 命令报错 su: Permission denied 如下图: su 命令 报错 su: Permission denied,不管是su普通用户还是r ...
- 10、jmeter的 Http的请求默认值
在我们测试过程当中,有很多HTTP协议的请求 这些请求 有很多比如说网址(url)都是相同的 端口也是相同的,路径可能也是相同的 这个时候就需要用到请求默认值,后续直接用就可以 不需要再去配置 后续 ...
- ELK集群基础环境初始化
集群基础环境初始化 1.准备虚拟机 192.168.1.7 192.168.1.6192.168.1.183 2.切换为国内centos源 3.修改sshd服务优化 [root@elk01 ~]# s ...
- 安装mysql8.0
安装repo源 参考mysql官方文档 参考文章 redhat7通过yum安装mysql5.7.17教程:https://www.jb51.net/article/103676.htm mysql r ...
- C++ condition_variable
一.使用场景 在主线程中创建一个子线程去计数,计数累计100次后认为成功,并告诉主线程:主线程收到计数100次完成的信息后继续往下执行 二.条件变量的成员函数 wait:当前线程调用 wait() 后 ...
- UAC的详细讲解(转载)
win32中也有对UAC的操作方法 网址:https://blog.csdn.net/zuishikonghuan/article/details/46965159?locationNum=7& ...
- ubuntu系统使用 sudo: cd:找不到命令
1. https://blog.csdn.net/sazass/article/details/125694492 https://blog.csdn.net/weixin_34033624/arti ...
- MacOS ssh config 配置
Host 别名 #password 注释,保存密码 HostName IP User 服务器账号#root Port 端口 IdentityFile ~/.ssh/id_rsa #指定密钥 Remot ...
- ES2016-ES2020
参考:https://zhuanlan.zhihu.com/p/59096242 备注:可以使用ES6取代的10个Lodash特性 https://www.w3cplus.com/javascript ...
- java8-并行计算
java8提供一个fork/join framework,fork/join框架是ExecutorService接口的一个实现,它可以帮助你充分利用你电脑中的多核处理器,它的设计理念是将一个任务分割成 ...