苍穹外卖-缓存和购物车

苍穹外卖-缓存

用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大

为了解决这个问题,我们通过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的生成与注解的属性cacheNameskey有关

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

苍穹外卖-购物车功能

在购物车表中设计冗余字段,可以增加查询的效率

  • 判断新增菜品/套餐在购物车是否存在
    • 存在,则数量加一
    • 不存在,则判断是套餐还是菜品
      • 是套餐,则根据套餐查询信息,将套餐插入到购物车中
      • 是菜品,则根据菜品查询信息,将菜品插入到购物车中