前言:
...
今日完成任务:
- 缓存菜品
- 缓存套餐
- 购物车相关接口CRUD
今日收获:
1.缓存菜品
用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力 随之增大。因此我们可以使用缓存,将数据库的部分数据直接保存到缓冲 中,需要访问的话,直接去缓冲中查找,减小数据库访问的压力。
下面是使用Redis来缓存数据,减小数据库访问压力的流程:

对具体业务情况分析:
菜品是通过分类id查询出来的,所有我们需要对根据分类id
进行查询菜品这一操作,进行缓存。
- 每个分类下的菜品保存一份缓存数据
- 数据库中菜品数据有变更时清理缓存数据
这里在Redis存储的时候,按照(String,String)
的方式进行传入就可。在JAVA中,操作数据库的时候我们以什么数据结构进行存储的同时以什么数据结构接受就可以。这里为什么数据类型不一致呢?
1)首先这种情况并不矛盾,Redis 的"String"是二进制安全的"字节流" 。Redis中是String,不只能存放字符串文本,像是"Hello"这种,而是可以存放任何二进制数据。像序列化后的JAVA对象,JOSN字符串等。
2)RedisTemplate的序列化机制RedisTemplate
默认使用的是 JDKSerializationRedisSerializer 或你可以配置为 Jackson2JsonRedisSerializer / GenericJackson2JsonRedisSerializer。
这意味着:
- 存入时:
List<DishVO>
→ 被自动序列化成字节数组(byte[]) → 存入 Redis 的 String 类型中 - 取出时:字节数组(byte[]) → 被反序列化回
List<DishVO>
3)为什么不用 Redis 的 List 数据结构,保持数据类型一致?
使用场景 | 推荐方式 |
---|---|
把一个 Java 列表对象整体缓存(如查询结果) | 用 String 类型 + 序列化 |
需要频繁对列表进行增删改(如消息队列、排行榜) | 用 Redis List 结构 |

下面就是具体代码实现,和上方流程保持一致

添加完缓存之后,会存在一种问题。就是当我们对数据库的数据进行增、删和改操作的时候,数据库中数据发生了改变,但是缓存中数据似乎还没有改变,因此会导致我们读取到旧数据。
为了解决这种问题,我们需要定时清理缓存 ?这个目前不太了解,只是在初始Redis
部分,等学完外卖再去深入学习Redis。目前的解决办法就是:每次增、删和改操作的时候,直接把对于的缓存全部删除。(似乎这种情况会导致大量key无效,数据库压力激增...)
在admin/DishController
中 定义统一清理缓存的私有方法
java
/**
* 统一清理缓存方法
* @param pattern 查找模式
*/
private void cleanCache(String pattern) {
try {
Set keys = redisTemplate.keys(pattern);
if (keys != null && !keys.isEmpty()) {
redisTemplate.delete(keys);
log.info("缓存清理成功,匹配键: {}", keys);
} else {
log.warn("未找到匹配的缓存键: {}", pattern);
}
} catch (Exception e) {
log.error("缓存清理失败", e);
}
}
在进行增删改操作的时候,删除对于的缓存就可。新增的话只需要删除对应菜品分类下的缓存 就可,对于删除和修改这种比较复杂,直接全部清空缓存。
下面是一个例子,直接调用cleanCache
方法即可

2.使用SpringCache完成缓存套餐
Spring Cache
是一个框架,实现了基于注解的缓存功能 ,只需要简单地加一个注解,就能实现缓存功能。底层可以操作不同的缓存:操作哪种类型的缓存,只需要引入对应的依赖就可。SpringBoot的起步依赖,无需指定版本
java
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
常用注解:

下面就看一下这几个注解用法吧,介绍上面都有
@CachePut
作用在方法上,将方法的返回值 放到缓存中,通过参数动态设置缓存的Key,这里的Key为spel表达式 ,一般是用于新增
java
@CachePut(cacheNames = "setmealCache", key="#setmealDTO.categoryId")
@Cacheable
查询套餐,在缓存中查找有无该数据,没有的话才会去执行方法体内容调Service执行Mapper...等操作,查找数据库中数据,然后保存到Redis缓存
中。这里用到了动态代理技术 ,生成了该Controller类的一个代理类,对该方法进行了增强。
java
@Cacheable(cacheNames = "setmealCache",key = "#categoryId") //key:setmealCache::categoryId
@CacheEvict
删除全部缓存,用于更新和修改方法上
java
@CacheEvict(cacheNames = "setmealCache",allEntries = true)
3.购物车相关接口
这里着重说一下添加购物车时的业务操作,因为是第一次接触这种比较复杂的业务。
重点:用户的user_id
和Dish/Setmeal
唯一决定购物车数据的一条数据
java
@Override
@Transactional
public void add(ShoppingCartDTO shoppingCartDTO) {
ShoppingCart shoppingCart = new ShoppingCart();
BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
//获取当前用户id
Long userId = BaseContext.getCurrentId();
shoppingCart.setUserId(userId);
//1.判断当前菜品/套餐是否存在
//动态SQL 通过口味、菜品id、套餐id等获取数据
List<ShoppingCart> list = shoppingCartMapper.getById(shoppingCart);
//2.存在则数量加1
if(list != null && !list.isEmpty()){
//加1
ShoppingCart sp = list.get(0);
sp.setNumber(sp.getNumber() + 1);
shoppingCartMapper.updateById(sp);
}else{
//3.不存在插入数据到购物车中
Long dishId = shoppingCart.getDishId();
if(dishId != null){
//添加的是菜品
DishVO dish = dishService.getById(dishId);
shoppingCart.setName(dish.getName());
shoppingCart.setImage(dish.getImage());
shoppingCart.setAmount(dish.getPrice());
}else{
//添加的是套餐
SetmealVO setmeal = setMealService.getById(shoppingCart.getSetmealId());
shoppingCart.setName(setmeal.getName());
shoppingCart.setImage(setmeal.getImage());
shoppingCart.setAmount(setmeal.getPrice());
}
//统一设置属性
shoppingCart.setNumber(1);
shoppingCart.setCreateTime(LocalDateTime.now());
shoppingCartMapper.insert(shoppingCart);
}
}
杂项知识点:
无
总结:
学习了新的技术,Redis缓存的具体应用和Spring Cache
解决缓存问题。我觉得相比于直接操作redis,使用Spring Cache
这个基于注解的框架非常方便。接触到了新增购物车这个接口,这个业务感觉是目前接触到比较复杂的了,虽然简单三步操作,但是需要对这个产品原型、数据库设计等有一个较深的了解。