首页 > 其他分享 >day02-2-商铺查询缓存

day02-2-商铺查询缓存

时间:2023-04-19 22:24:56浏览次数:41  
标签:缓存 hmdp day02 商铺 private import com id

功能02-商铺查询缓存

3.商铺详情缓存查询

3.1什么是缓存?

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

缓存的作用:

  1. 降低后端负载
  2. 提高读写效率,降低响应时间

缓存的成本:

  1. 数据一致性成本
  2. 代码维护成本
  3. 运维成本

3.2需求说明

如下,当我们点击商店详情的时候,前端会向后端发出请求,后端需要把相关的商店数据返回给客户端显示。

image-20230419181234455 image-20230419181346154

3.3思路分析(添加Redis缓存)

使用Redis的缓存模型如下:

当客户端发送请求到服务端时,先去redis中查询有没有对应的数据:

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

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,查询较快

image-20230419184634104 image-20230419184803863

4.商铺类型缓存查询

4.1需求说明

店铺类型在首页和其他多个页面都会用到,如下:

image-20230419185029737

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

image-20230419185352553 image-20230419191116333

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)测试,访问客户端首页,

返回的数据如下:

image-20230419192833395 image-20230419192739478

5.缓存更新

5.1缓存更新策略

image-20230419203111144

5.1.1主动更新策略

  • Cache Aside Pattern:由缓存的调用者,在更新数据库的同时更新缓存(可控性最高,推荐使用)
  • Read/Write Through Pattern:缓存与数据库整合为一个服务,由服务来维护一致性。调用者调用该服务,无需关心缓存一致性问题
  • Write Behind Caching Pattern:调用者只操作缓存,由其他线程异步的将缓存数据持久化到数据库,保证最终一致

操作缓存和数据库时有三个问题需要考虑:

  1. 删除缓存还是更新缓存?

    • 更新缓存:每次更新数据库都更新缓存,无效写操作较多
    • 删除缓存:更新数据库时让缓存失效,查询时再更新缓存(推荐使用)
  2. 如何保证缓存与数据库的操作的同时成功或失败?(原子性)

    • 单体系统,将缓存与数据库操作放在一个事务
    • 分布式系统,利用TCC等分布式事务方案
  3. 先操作缓存还是先操作数据库?(线程安全问题)

    image-20230419211116080

    如上,虽然两种方案都有可能造成缓存和数据库不一致,但更推荐先更新数据库再删除缓存。

    先更新数据库再删除缓存出现数据不一致概率更低,因为操作缓存一般比数据库更快,所以发生右图的情况很低(右图)。即使发生了,可以配合TTL定时清除缓存。

5.1.2总结

缓存更新策略的最佳实践方案:

  1. 低一致性需求:使用Redis自带的内存淘汰机制即可
  2. 高一致性需求:主动更新,并以超时剔除作为兜底方案
    • 读操作:
      • 缓存命中则直接返回
      • 缓存未命中则查询数据库,并写入缓存,设定超时时间
    • 写操作:
      • 先写数据库,然后再删除缓存
      • 要确保数据库与缓存操作的原子性

5.2需求说明

给查询商铺的缓存添加超时剔除和主动更新策略:

  1. 根据id查询店铺时,如果缓存未命中,则查询数据库,将数据库结果写入缓存,并设置超时时间
  2. 根据id修改店铺,先修改数据库,再删除缓存

5.3代码实现

(1)修改ShopServiceImpl的queryById()方法,设置超时时间

image-20230419213411895

并添加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

image-20230419215427668

写操作:使用postman向服务端发送更新店铺信息请求,可以看到当更新数据时候,先更新数据库,然后将redis的缓存删除。之后如果再有查询,将会重建redis的缓存,实现数据的一致性。

标签:缓存,hmdp,day02,商铺,private,import,com,id
From: https://www.cnblogs.com/liyuelian/p/17334849.html

相关文章

  • 缓存与数据库双写一致性几种策略分析
    作者:京东零售 于泷一、背景在高并发场景中,为防止大量请求直接访问数据库,缓解数据库压力,常用的方式一般会增加缓存层起到缓冲作用,减少数据库压力。引入缓存,就会涉及到缓存与数据库中数据如何保持一致性问题,本文将对几种缓存与数据库保证数据一致性的使用方式进行分析。为保证高......
  • 6.1.4 MySQL缓存策略
    LinuxC/C++服务器MySQL缓存策略大部分场景下MySQL的读要远远大于写的需求的,急需要解决的问题是提升读的性能......
  • ASP.NET Core - 缓存之分布式缓存
    分布式缓存是由多个应用服务器共享的缓存,通常作为访问它的应用服务器的外部服务进行维护。分布式缓存可以提高ASP.NETCore应用的性能和可伸缩性,尤其是当应用由云服务或服务器场托管时。与其他将缓存数据存储在单个应用服务器上的缓存方案相比,分布式缓存具有多个优势。当分发......
  • 【GIT】学习day02 | git环境搭建并将项目进行本地管理
    进入终端输入GitHub或者给gitee的用户名和邮箱地址然后依次敲入一下信息gitcommit-m"initproject"gitinitgitadd. 正确结果如下 ......
  • 缓存空间优化实践
    作者:京东科技董健导读缓存Redis,是我们最常用的服务,其适用场景广泛,被大量应用到各业务场景中。也正因如此,缓存成为了重要的硬件成本来源,我们有必要从空间上做一些优化,降低成本的同时也会提高性能。下面以我们的案例说明,将缓存空间减少70%的做法。场景设定1、我们需要将POJO存储到缓......
  • http 缓存方案
    1、强缓存:强制缓存由Cache-Control,Exipres(HTTP1.0)控制。浏览器直接读本地缓存2、协商缓存:协商缓存由Last-Modified/IfModified-Since,Etag/If-None-Match实现,每次请求需要让服务器判断一下资源是否更新过,从而决定浏览器是否使用缓存,如果是,则返回304,否则重新完整响应。在H......
  • Linux服务器如何清除dns缓存
    DNS缓存是一个临时数据库,用于存储已解释的DNS查询信息。换句话说,每当你访问网站时,你的操作系统和网络浏览器都会保留域名和相应IP地址的记录。这消除对远程DNS服务器重复查询,并允许你的操作系统或浏览器快速解析网站的域名。但是在某些情况下,例如对网络问题进行故障排除或者在更改D......
  • SqlServer 从执行计划缓存发现问题sql
    注意以下sql都较复杂,在数据库中执行时间可能较长,不要执行过于频繁。一、隐式转换sql版本1--找到含有隐式转换且会导致indexseek变为indexscan的语句–findalltheplansandquerywhicharebeingcomparedwithwrongdatatypeinqueriesandthuscasuingtheconversio......
  • 15天玩转redis —— 第七篇 同事的一次缓存操作引起对慢查询的认识
       上个星期同事做一个业务模块,需要将一个80M的数据存入到redis缓存中,想法总是好的,真操作的时候遇到了HSet超时,我们使用的是C#的StackExchange.Redis驱动。<redisCacheClientallowAdmin="true"ssl="false"connectTimeout="5000"abortConnect="false"database="......
  • 学习记录:第四周day02笔记
    文件的分类文本文件:是人能看懂的文件,存储的是字符符号的ASCII码的二进制二进制文件:存储的是数据的补码的二进制 文件IOFILE*fopen(constchar*path,constchar*mode);功能:打开或创建文件参数:path:文件的路径,以字符串形式提供mode:打开模式,以字符串形式提供r......