苍穹外卖-缓存和购物车
苍穹外卖-缓存
用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大
为了解决这个问题,我们通过Redis来缓存菜品数据,减少数据库的查询操作
当用户查询数据时,先找缓存中,如果没有再将数据库中数据查找到放入缓存中
当其他用户查询时,缓存中已经存在数据了
实现思路
每一个分类的菜品保存在一个缓存数据中(存在一个key中),使用分类id作为缓存的key
当数据库中的菜品发生变更时清理缓存数据
业务代码
Redis中的String类型并不是Java意义上的String字符串类型
我们使用Java对Redis的String类型数据进行存放,可以直接存放对象类型的数据,怎么存进去怎么取出来即可
其中的原理就是将对象数据进行一个序列化,然后存储,取出来的时候进行反序列化的过程
/**
* 根据分类id查询菜品
*
* @param categoryId
* @return
*/
@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result<List<DishVO>> list(Long categoryId) {
// 构造redis中的key,规则:dish_分类id
String key = "dish_" + categoryId;
List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);
if (list != null && list.size() > 0) {
// 查询到直接返回
return Result.success(list);
}
// 如果不存在,则查询数据库
Dish dish = new Dish();
dish.setCategoryId(categoryId);
dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品
list = dishService.listWithFlavor(dish);
// 将数据放入redis中缓存
redisTemplate.opsForValue().set(key, list);
return Result.success(list);
}
数据变更
在对数据库中的菜品进行增删改的操作时,需要清理缓存数据
但无需清理掉所有的缓存数据,对哪个分类(Key)进行操作,就清理掉这个Key的缓存数据
// 清理缓存数据
String key = "dish_" + dishDTO.getCategoryId();
redisTemplate.delete(key);
注:在存放缓存数据时,我们在Service层操作,而清空返回数据我们在Controller层操作(感觉在Service层操作也可以)
删除操作
在进行删除操作时,我们需要根据分类id清除缓存数据,但是我们删除时拿不到分类id,还要进行数据库查询之后再拿id就十分麻烦
所以我们在进行删除操作时,直接删除所有缓存数据即可
// 清理所有缓存数据
Set keys = redisTemplate.keys("dish_*"); // 查询以dish_开头的key
redisTemplate.delete(keys); // 以set集合作为参数批量删除缓存数据
修改操作
当修改一些菜品信息时,删除缓存数据就会很简单
但如果修改分类时,会影响到缓存中两个分类中的菜品数据
所以我们也是干掉所有的缓存数据
cleanCache()
抽取成为cleanCache方法
private void cleanCache(String pattern) {
Set keys = redisTemplate.keys(pattern);
redisTemplate.delete(keys);
}
// 清理所有缓存数据
cleanCache("dish_*");
// 清理缓存数据
String key = "dish_" + dishDTO.getCategoryId();
cleanCache(key);
Spring Cache
Spring提供了缓存功能的框架实现,只需要简单实用注解,就能实现缓存功能
Spring Cache底层可以切换不同的缓存实现:
- EHCache
- Caffeine
- Redis
有趣的是,我们并不需要针对不同的底层而具体编写配置文件,只需要导入Redis的Java依赖就会被SpringCache自动识别并应用
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
常用注解
注解 | 说明 |
---|---|
@EnableCaching | 加在启动类上,用以开启注解功能 |
@Cacheable | 在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法的返回值放到缓存中 |
@CachePut | 将方法的返回值放到缓存中 |
@CacheEvict | 将一条或多条数据从缓存中删除 |
key的生成
如果使用SpringCache缓存数据,key的生成与注解的属性cacheNames
、key
有关
cacheNames::key
在redis中,冒号分隔的字符会被逻辑分层
而key的值不可以是唯一的,否则所有缓存都会放到同一个key中
所以使用spEL(Spring表达式语言)来动态获取key
@CachePut(cacheNames = "userCache" , key = "#user.id") // 使用spEL获取形参对象user的id属性
直接使用参数对象的id也是没有问题的,操作缓存数据是等到方法执行玩才会操作,这样主键值会回显回来
spEL
表达式 | 说明 |
---|---|
#result | 当前方法的返回值 |
#形参 | 获取方法的参数 |
#p0 | 获取方法的第一个参数 |
#a0 | 获取方法的第一个参数 |
#root.arg[0] | 获取方法的第一个参数 |
代理对象
Spring Cache的本质是为加入注解的方法生成一个代理,在执行具体的方法前,先对Redis中的缓存进行一些操作
删除
除了使用@CacheEvict注解删除单个缓存数据之外,还能删除所有缓存数据
使用allEntries = true属性来控制是否删除所有key
CacheEvict(cacheNames = "userCache" , allEntries = true) // 删除userCache的所有key
苍穹外卖-购物车功能
在购物车表中设计冗余字段,可以增加查询的效率
- 判断新增菜品/套餐在购物车是否存在
- 存在,则数量加一
- 不存在,则判断是套餐还是菜品
- 是套餐,则根据套餐查询信息,将套餐插入到购物车中
- 是菜品,则根据菜品查询信息,将菜品插入到购物车中