Redis的缓存问题(一)添加redis缓存与扩展
什么是缓存?
缓存就是数据交换的缓冲区(称作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的视频留了一道练习题,我们在这里将其实现以下:
视频地址如下
代码详情
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的分享