Redis的缓存问题(一)添加redis缓存与扩展

什么是缓存?

缓存的作用与成本

作用

成本

如何添加redis缓存?

缓存作用模型

需求分析

代码实现

ShopController

IShopService 业务层接口

ShopServiceImpl 业务层实现类

扩展功能实现

视频地址如下 

代码详情

完整代码,需要自取(完整无套路)


什么是缓存?

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

缓存的作用与成本

作用

  •  当请求进入Tomcat以后,以前是查询数据库,而数据库本身是存在磁盘中的,查询需要进行IO操作,耗时,会给数据库造成压力;但是当我们有了缓存,请求进入Tomcat以后,直接进入Redis,查到后将结果返回给前端,就减轻了后端的负载。
  • 基于内存,所以读写快!

成本

  • 同一份数据在Redis与数据库中是要保证一致的!
  • 代码会更加复杂。
  • 为了高可用、防止雪崩,可能需要搭建集群,需要运维成本。

如何添加redis缓存?

缓存作用模型

先查询Redis,如果命中,直接将数据取出;反之未命中,则查询MySQL数据库,若有数据返回客户端,并且将其写入Redis缓存中。

需求分析

查询导航栏,点击后查看

我们需要的就是给API添加缓存 

代码实现

ShopController

之前是直接访问数据库,如下:

@GetMapping("/{id}")
public Result queryShopById(@PathVariable("id") Long id) {
    return Result.ok(shopService.getById(id));
}

但是这里需要用到redis,步骤比较多,所以我们在Controller层需要调用Service层去实现该逻辑。

@GetMapping("/{id}")
public Result queryShopById(@PathVariable("id") Long id) {
    //return Result.ok(shopService.getById(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 {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result queryById(Long id) {

        // 1.从redis查询商铺缓存
        String key = CACHE_SHOP_KEY + id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);

        // 2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            // 3.存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            System.out.println("Redis");
            return Result.ok(shop);
        }

        // 4.不存在,根据id查询数据库
        Shop shop = getById(id);

        // 5.不存在,返回错误
        if (shop == null) {
            return Result.fail("店铺不存在!");
        }

        // 6.存在,写入Redis
        // 把shop转换成为JSON形式写入Redis
        System.out.println("MySQL");
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));

        return Result.ok(shop);
    }
}

如下所示: 这些数据一旦被访问就会被写入Redis中。

此处,redis的存储结构我们采用的是string的结构(当然使用Hash也是可以的) 

我们可以看看打印控制台

显然第一次查询执行了SQL,第二次就是直接从Redis去数据了!

Preparing: SELECT id,name,type_id,images,area,address,x,y,avg_price,sold,comments,score,open_hours,create_time,update_time FROM tb_shop WHERE id=?

日志总览 

2022-07-06 14:12:00.497 DEBUG 7464 --- [nio-8081-exec-4] c.h.m.VoucherMapper.queryVoucherOfShop   : ==>  Preparing: SELECT v.`id`, v.`shop_id`, v.`title`, v.`sub_title`, v.`rules`, v.`pay_value`, v.`actual_value`, v.`type`, sv.`stock` , sv.begin_time , sv.end_time FROM tb_voucher v LEFT JOIN tb_seckill_voucher sv ON v.id = sv.voucher_id WHERE v.shop_id = ? AND v.status = 1
2022-07-06 14:12:00.498 DEBUG 7464 --- [nio-8081-exec-4] c.h.m.VoucherMapper.queryVoucherOfShop   : ==> Parameters: 1(Long)
2022-07-06 14:12:00.500 DEBUG 7464 --- [nio-8081-exec-4] c.h.m.VoucherMapper.queryVoucherOfShop   : <==      Total: 1
2022-07-06 14:12:00.500 DEBUG 7464 --- [nio-8081-exec-2] com.hmdp.mapper.ShopMapper.selectById    : ==>  Preparing: SELECT id,name,type_id,images,area,address,x,y,avg_price,sold,comments,score,open_hours,create_time,update_time FROM tb_shop WHERE id=?
2022-07-06 14:12:00.500 DEBUG 7464 --- [nio-8081-exec-2] com.hmdp.mapper.ShopMapper.selectById    : ==> Parameters: 1(Long)
2022-07-06 14:12:00.503 DEBUG 7464 --- [nio-8081-exec-2] com.hmdp.mapper.ShopMapper.selectById    : <==      Total: 1
MySQL
2022-07-06 14:12:05.146 DEBUG 7464 --- [nio-8081-exec-3] c.h.m.VoucherMapper.queryVoucherOfShop   : ==>  Preparing: SELECT v.`id`, v.`shop_id`, v.`title`, v.`sub_title`, v.`rules`, v.`pay_value`, v.`actual_value`, v.`type`, sv.`stock` , sv.begin_time , sv.end_time FROM tb_voucher v LEFT JOIN tb_seckill_voucher sv ON v.id = sv.voucher_id WHERE v.shop_id = ? AND v.status = 1
2022-07-06 14:12:05.146 DEBUG 7464 --- [nio-8081-exec-3] c.h.m.VoucherMapper.queryVoucherOfShop   : ==> Parameters: 1(Long)
2022-07-06 14:12:05.148 DEBUG 7464 --- [nio-8081-exec-3] c.h.m.VoucherMapper.queryVoucherOfShop   : <==      Total: 1
Redis

扩展功能实现

如下这些logo一般是长期存在的,每次登入我们都要重新加载,所以这里将其缓存在Redis中,来提高网页的效率问题。

在黑马程序员的视频中,P37的视频留了一道练习题,我们在这里将其实现以下:

视频地址如下 

黑马程序员Redis入门到实战教程,全面透析redis底层原理+redis分布式锁+企业解决方案+redis实战_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1cr4y1671t?p=37&vd_source=470cf4bd5833d6feeb5c5aaefc5e3465

代码详情

ShopTypeController

调用Service层,在Service中去做具体的实现 

@RestController
@RequestMapping("/shop-type")
public class ShopTypeController {

    @Resource
    private IShopTypeService typeService;

    @GetMapping("list")
    public Result queryTypeList() {
//        List<ShopType> typeList = typeService
//                .query().orderByAsc("sort").list();
//        return Result.ok(typeList);

        return typeService.getByIconList();
    }
}

ShopTypeServiceImpl 

@Service
public class ShopTypeServiceImpl extends ServiceImpl<ShopTypeMapper, ShopType> implements IShopTypeService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result getByIconList() {

        // 1.在redis中间查询
        String key = CACHE_SHOP_TYPE_KEY;
        List<String> shopTypeList = new ArrayList<>();
        // range()中的 -1 表示最后一位
        // shopTypeList中存放的数据是[{...},{...},{...}...] 一个列表中有一个个json对象
        shopTypeList = stringRedisTemplate.opsForList().range(key,0,-1);

        // 2.判断是否缓存中了
        // 3.中了返回 (判断redis不空)
        if(!shopTypeList.isEmpty()) {
            List<ShopType> typeList = new ArrayList<>();
            for (String s : shopTypeList) {
                ShopType shopType = JSONUtil.toBean(s, ShopType.class);
                // shopType 是一个对象
                typeList.add(shopType);
            }
            return Result.ok(typeList);
        }

        // 4.redis未命中数据,从数据库中获取,根据ShopType对象的sort属性排序后存入typeList
        List<ShopType> typeList = query().orderByAsc("sort").list();
        // 5.数据库中如果不存在直接返回错误
        if(typeList.isEmpty()){
            return Result.fail("不存在分类");
        }

        for(ShopType shopType : typeList){
            String s = JSONUtil.toJsonStr(shopType);
            shopTypeList.add(s);
        }
        // 6.存在直接添加进缓存
        stringRedisTemplate.opsForList().rightPushAll(key, shopTypeList);
        return Result.ok(typeList);
    }
}

注意点: 

这里有一些东西需要解释一下,stringRedisTemplate.opsForList().range(key,0,-1); 中的range的 -1 代表的是最后一个的意思,即这些logo的数量为10个所以range(key,0,10);是一个意思。

代码实现思路与上面的样例类似,只是需要注意的是返回的 typeList 是一个 List 列表。

完整代码,需要自取(完整无套路)

链接:https://pan.baidu.com/s/14lH4igdPvwZikAApA6ztYA 
提取码:l2hh 
--来自百度网盘超级会员V4的分享

posted @ 2022-07-06 14:16  金鳞踏雨  阅读(81)  评论(0编辑  收藏  举报  来源