day02-2-商铺查询缓存

打印 上一主题 下一主题

主题 825|帖子 825|积分 2475

功能02-商铺查询缓存

3.商铺详情缓存查询

3.1什么是缓存?

缓存就是数据交换的缓冲区(称作Cache),是存储数据的临时地方,一般读写性能较高。
缓存的作用:

  • 降低后端负载
  • 提高读写效率,降低响应时间
缓存的成本:

  • 数据一致性成本
  • 代码维护成本
  • 运维成本
3.2需求说明

如下,当我们点击商店详情的时候,前端会向后端发出请求,后端需要把相关的商店数据返回给客户端显示。
3.3思路分析(添加Redis缓存)

使用Redis的缓存模型如下:
当客户端发送请求到服务端时,先去redis中查询有没有对应的数据:

  • 如果命中,则直接给客户端返回数据,这样直接访问数据库的请求就会大大减少
  • 如果未命中,则到数据库中查询,同时将数据写入redis,防止下一次查询同样的数据,然后将数据返回给客户端
3.4代码实现

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

4.1需求说明

店铺类型在首页和其他多个页面都会用到,如下:
要求当我们点击商铺类型的时候,前端会向后端发出请求,后端需要把相关的商店类型数据返回给客户端显示:
4.2思路分析

该功能的实现思路与上述的思路大体一致。
4.3代码实现

(1)实体类 ShopType
  1. package com.hmdp.entity;
  2. import com.baomidou.mybatisplus.annotation.IdType;
  3. import com.baomidou.mybatisplus.annotation.TableId;
  4. import com.baomidou.mybatisplus.annotation.TableName;
  5. import com.fasterxml.jackson.annotation.JsonIgnore;
  6. import lombok.Data;
  7. import lombok.EqualsAndHashCode;
  8. import lombok.experimental.Accessors;
  9. import java.io.Serializable;
  10. import java.time.LocalDateTime;
  11. /**
  12. * @author 李
  13. * @version 1.0
  14. */
  15. @Data
  16. @EqualsAndHashCode(callSuper = false)
  17. @Accessors(chain = true)
  18. @TableName("tb_shop_type")
  19. public class ShopType implements Serializable {
  20.     private static final long serialVersionUID = 1L;
  21.     //主键
  22.     @TableId(value = "id", type = IdType.AUTO)
  23.     private Long id;
  24.     //类型名称
  25.     private String name;
  26.     //图标
  27.     private String icon;
  28.     //顺序
  29.     private Integer sort;
  30.     //创建时间
  31.     @JsonIgnore
  32.     private LocalDateTime createTime;
  33.     //更新时间
  34.     @JsonIgnore
  35.     private LocalDateTime updateTime;
  36. }
复制代码
(2)ShopTypeMapper接口
  1. package com.hmdp.mapper;
  2. import com.hmdp.entity.ShopType;
  3. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  4. /**
  5. * Mapper 接口
  6. *
  7. * @author 李
  8. * @version 1.0
  9. */
  10. public interface ShopTypeMapper extends BaseMapper<ShopType> {
  11. }
复制代码
(3)服务类接口 IShopTypeService
  1. package com.hmdp.service;
  2. import com.hmdp.dto.Result;
  3. import com.hmdp.entity.ShopType;
  4. import com.baomidou.mybatisplus.extension.service.IService;
  5. /**
  6. * 服务类接口
  7. *
  8. * @author 李
  9. * @version 1.0
  10. */
  11. public interface IShopTypeService extends IService<ShopType> {
  12.     Result queryShopList();
  13. }
复制代码
(4)服务实现类 ShopTypeServiceImpl
  1. package com.hmdp.service.impl;
  2. import cn.hutool.core.util.StrUtil;
  3. import cn.hutool.json.JSONUtil;
  4. import com.hmdp.dto.Result;
  5. import com.hmdp.entity.ShopType;
  6. import com.hmdp.mapper.ShopTypeMapper;
  7. import com.hmdp.service.IShopTypeService;
  8. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  9. import org.springframework.data.redis.core.StringRedisTemplate;
  10. import org.springframework.stereotype.Service;
  11. import javax.annotation.Resource;
  12. import java.util.ArrayList;
  13. import java.util.List;
  14. import static com.hmdp.utils.RedisConstants.CACHE_SHOP_TYPE;
  15. /**
  16. * 服务实现类
  17. *
  18. * @author 李
  19. * @version 1.0
  20. */
  21. @Service
  22. public class ShopTypeServiceImpl extends ServiceImpl<ShopTypeMapper, ShopType> implements IShopTypeService {
  23.     @Resource
  24.     private StringRedisTemplate stringRedisTemplate;
  25.     @Override
  26.     public Result queryShopList() {
  27.         //查询redis中有没有店铺类型缓存
  28.         String shopTypeJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_TYPE);
  29.         //如果有,则将其转为对象类型,并返回给客户端
  30.         if (StrUtil.isBlank(shopTypeJson)) {
  31.             List<ShopType> shopTypeList = JSONUtil.toList(shopTypeJson, ShopType.class);
  32.             return Result.ok(shopTypeList);
  33.         }
  34.         //如果redis中没有缓存,到DB中查询
  35.         //如果DB中没有查到,返回错误信息
  36.         List<ShopType> list = query().orderByAsc("sort").list();
  37.         if (list == null) {
  38.             return Result.fail("查询不到店铺类型!");
  39.         }
  40.         //如果DB查到了数据
  41.         //将数据存入Redis中(转为json类型存入)
  42.         stringRedisTemplate.opsForValue()
  43.                 .set(CACHE_SHOP_TYPE, JSONUtil.toJsonStr(list));
  44.         //并返回给客户端
  45.         return Result.ok(list);
  46.     }
  47. }
复制代码
(5)控制类 ShopTypeController
  1. package com.hmdp.controller;
  2. import com.hmdp.dto.Result;
  3. import com.hmdp.entity.ShopType;
  4. import com.hmdp.service.IShopTypeService;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RestController;
  8. import javax.annotation.Resource;
  9. import java.util.List;
  10. /**
  11. * 前端控制器
  12. *
  13. * @author 李
  14. * @version 1.0
  15. */
  16. @RestController
  17. @RequestMapping("/shop-type")
  18. public class ShopTypeController {
  19.     @Resource
  20.     private IShopTypeService typeService;
  21.     @GetMapping("list")
  22.     public Result queryTypeList() {
  23.         return typeService.queryShopList();
  24.     }
  25. }
复制代码
(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()方法如下:
  1. @Override
  2. @Transactional
  3. public Result update(Shop shop) {
  4.     Long id = shop.getId();
  5.     if (id == null) {
  6.         return Result.fail("店铺id不能为空");
  7.     }
  8.     //1.更新数据库
  9.     updateById(shop);
  10.     //2.删除redis缓存
  11.     stringRedisTemplate.delete(CACHE_SHOP_KEY + id);
  12.     return Result.ok();
  13. }
复制代码
(2)修改IShopService,添加方法声明
  1. Result update(Shop shop);
复制代码
(3)修改ShopController,添加方法
  1. /**
  2. * 更新商铺信息
  3. * @param shop 商铺数据
  4. * @return 无
  5. */
  6. @PutMapping
  7. public Result updateShop(@RequestBody Shop shop) {
  8.     // 写入数据库
  9.     return shopService.update(shop);
  10. }
复制代码
(4)测试
读操作:首次访问店铺详情,可以看到redis中存入数据,并且设置了TTL
写操作:使用postman向服务端发送更新店铺信息请求,可以看到当更新数据时候,先更新数据库,然后将redis的缓存删除。之后如果再有查询,将会重建redis的缓存,实现数据的一致性。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

魏晓东

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表