当前位置: 首页 > news >正文

苍穹外卖项目实战(day7-1)-缓存菜品和缓存套餐功能-记录实战教程、问题的解决方法以及完整代码


完整资料下载
通过网盘分享的文件:苍穹外卖
链接:

https://pan.baidu.com/s/1JJaFOodXOF_lNJSUiZ6qtw?pwd=ps2t

提取码: ps2t


目录

1、缓存菜品

(1)问题说明

(2)使用redis缓存部分数据

1-2、代码完善

(1)user包下DishController完善

(2)RedisConfiguration完善

(3)admin包下DishController

(4)测试功能

2、缓存套餐

Spring Cache知识点

2-2、代码完善

(1)user/SetmealController完善

(2)admin/SetmealController完善

(3)功能测试


1、缓存菜品

(1)问题说明

(2)使用redis缓存部分数据

注意:

1、访问mysql是磁盘IO操作,访问redis是访问内存

2、用户点单的购物车中的菜品需要存储,这个储存不需要给服务器端,只有最后点付钱才会给服务器提交数据所以这里才有Redis来暂存

3、面试题:如何保持radis与数据库中的数据一致性:设置过期时间 延迟双删

4、在套菜中,套餐是一个集合list,我们将list集合进行序列化,转成Redis的String类型再存进Redis中;

redis的string不是java的string,redis的string算是object了,外部任何数据传入redis都会被转换为string

5、注意:小程序的图片默认访问的是老师的oss,直接去数据库把图片oss链接全改成自己的,或者在管理端(前端页面)重新上传图片

6、 从redis中获取商铺营业状态
 需要提前在redis中设置key为shop_status,value为1或0,1表示营业中,0表示打烊中,否则会报错,错误提示为

“Cannot invoke "java.lang.Integer.intValue()" because "status" is null”

7、现在有两个问题;

(1)如果数据库数据变动了怎么办?

 数据库的数据变动了,做新增修改的时候都需要先更新数据库,然后删除缓存,延时双删,每次查询再把数据存入redis。

(2)如果不断存入新数据,数据不会老化那么总有一天内存会爆满的,如何处理?

 设置超时时间,到期了数据就会消失,还可以更改redis的淘汰策略

1-2、代码完善

(1)user包下DishController完善

位置:sky-server/src/main/java/com/sky/controller/user/DishController.java

完整代码:

package com.sky.controller.user;import com.sky.constant.StatusConstant;
import com.sky.entity.Dish;
import com.sky.result.Result;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;@RestController("userDishController")
@RequestMapping("/user/dish")
@Slf4j
@Api(tags = "C端-菜品浏览接口")
public class DishController {@Autowiredprivate DishService dishService;@Autowiredprivate RedisTemplate redisTemplate;/*** 根据分类id查询菜品* @param categoryId* @return*/@GetMapping("/list")@ApiOperation("根据分类id查询菜品")public Result<List<DishVO>> list(Long categoryId) {log.info("根据分类id查询菜品,categoryId={}", categoryId);// 查询Redis缓存//构造缓存keyString key = "dish_" + categoryId;//从缓存中获取数据List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);// 判断是否存在缓存,如果存在,则直接返回缓存数据,无需再次查询数据库if (list != null && list.size() > 0) {log.info("查询Redis缓存,key={},list={}", key, list);return Result.success(list);}// 查询数据库Dish dish = new Dish();dish.setCategoryId(categoryId);dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品//如果缓存中没有数据,则查询数据库list = dishService.listWithFlavor(dish);log.info("查询数据库,categoryId={},list={}", categoryId, list);// 保存到Redis缓存redisTemplate.opsForValue().set(key, list);return Result.success(list);}}

示意图:

 


启动项目时遇见的错误:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: java.util.ArrayList[0]->com.sky.vo.DishVO["updateTime"])


原因:

         这个错误是因为 Jackson 默认不支持 Java 8 的日期时间类型(如LocalDateTime)序列化导致的。错误信息已经提示了解决方案:需要添加jackson-datatype-jsr310模块来支持 Java 8 日期时间类型的处理。


 解决方法一:

        在RedisTemplate,设置了键和值的序列化方式。从错误来看,这个配置中缺少了对 Java 8 日期时间类型(如LocalDateTime)的支持,需要添加JavaTimeModule来解决序列化问题。代码在下面的“(2)RedisConfiguration完善”有步骤

 解决方法二:

        取消对值的序列化,只保留对key的序列化,这样就可以避免日期时间类型(如LocalDateTime)序列化导致的错误

在小程序查看菜品是,Redis就会缓存对应的数据:值没有序列化

(2)RedisConfiguration完善

位置:sky-server/src/main/java/com/sky/config/RedisConfiguration.java

添加的代码:

    // 注册JavaTimeModule以支持LocalDateTime等Java 8日期类型objectMapper.registerModule(new JavaTimeModule());

文件完整代码:

package com.sky.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
@Slf4j
public class RedisConfiguration {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {log.info("初始化创建Redis模板对象...");RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper objectMapper = new ObjectMapper();// 注册JavaTimeModule以支持LocalDateTime等Java 8日期类型objectMapper.registerModule(new JavaTimeModule());objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(objectMapper);// 设置key的序列化方式redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// 设置hash类型的序列化方式redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);redisTemplate.afterPropertiesSet();return redisTemplate;}
}

示意图:

查看Redis数据库:键和值都已经序列化

(3)admin包下DishController

         传入的菜品id,redis存的key是类别id,如果我们想删除菜品对应分类的缓存数据 需要我们去查询数据库 也需多次访问数据库 我们的目的是减去数据库操作压力,故我们选择清理所有缓存数据

位置:sky-server/src/main/java/com/sky/controller/admin/DishController.java

新增代码:

/*** 清除所有Redis缓存* @return*/private void clearRedisCache(String pattern) {Set keys = redisTemplate.keys(pattern);redisTemplate.delete(keys);}

完整代码:

package com.sky.controller.admin;import com.sky.dto.DishDTO;
import com.sky.dto.DishPageQueryDTO;
import com.sky.entity.Dish;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;import java.util.List;
import java.util.Set;/*** 后台菜品管理* @author sky*/
@RestController//声明当前类是一个控制器
@RequestMapping("/admin/dish")//声明当前类下所有请求的前缀
@Api(tags = "后台菜品管理")
@Slf4j//声明当前类使用log4j日志
public class DishController {@Autowiredprivate DishService dishService;@Autowiredprivate RedisTemplate redisTemplate;/*** 新增菜品* (1)@RequestBody:注解用于将请求体中的json数据绑定到对象中,这里的对象就是DishDTO* 而且前端发送的数据是Json格式,所以需要将请求体中的json数据绑定到DishDTO对象中* (2)使用DishDTO对象来接收前端发送的json数据的原因是:* 1. 方便后续处理,比如校验数据,保存到数据库等* 2. 前端发送的json数据可能有多余的字段,这些字段在后续的业务逻辑中可能并不需要,* 所以使用DishDTO对象来接收前端发送的json数据,可以过滤掉多余的字段,提高后续业务逻辑的效率* @param dishDTO* @return*/@PostMapping//声明当前方法是一个HTTP POST请求@ApiOperation(value = "新增菜品")public Result save(@RequestBody DishDTO dishDTO){log.info("新增菜品:{}", dishDTO);dishService.saveWithFlavors(dishDTO);//删除Redis缓存clearRedisCache("dish_" + dishDTO.getCategoryId());return Result.success();}/*** 分页查询菜品* @param dishPageQueryDTO* @return*/@GetMapping("/page")@ApiOperation("分页查询菜品")public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO){log.info("分页查询菜品:{}", dishPageQueryDTO);PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);return Result.success(pageResult);}/*** 菜品批量删除*传入的菜品id,redis存的key是类别id,如果我们想删除菜品对应分类的缓存数据* 需要我们去查询数据库 也需多次访问数据库* 我们的目的是减去数据库操作压力,故我们选择清理所有缓存数据* @param ids* @return*/@DeleteMapping@ApiOperation("菜品批量删除")public Result delete(@RequestParam List<Long> ids) {log.info("菜品批量删除:{}", ids);dishService.deleteBatch(ids);//删除所有Redis缓存clearRedisCache("dish_*");return Result.success();}/*** 根据ID查询菜品详情* @param id* @return*/@GetMapping("/{id}")@ApiOperation("根据ID查询菜品详情")public Result<DishVO> getById(@PathVariable Long id){log.info("查询菜品:{}", id);DishVO dishVO = dishService.getByIdWithFlavors(id);return Result.success(dishVO);}/*** 更新(修改)菜品* @param dishDTO* @return*/@PutMapping@ApiOperation("更新菜品")public Result update(@RequestBody DishDTO dishDTO){log.info("更新菜品:{}", dishDTO);dishService.updateWithFlavors(dishDTO);
//        //删除所有Redis缓存
//        Set keys = redisTemplate.keys("dish_*");
//        redisTemplate.delete(keys);//删除所有Redis缓存clearRedisCache("dish_*");return Result.success();}/*** 根据分类id查询套餐* @param categoryId* @return*/@GetMapping("/list")@ApiOperation("根据分类id查询套餐")public Result<List<Dish>> list(Long categoryId){log.info("根据分类id查询套餐:{}", categoryId);List<Dish> list = dishService.list(categoryId);return Result.success(list);}/*** 菜品起售停售* @param status* @param id* @return*/@PostMapping("/status/{status}")@ApiOperation("菜品起售停售")public Result<String> startOrStop(@PathVariable Integer status, Long id){dishService.startOrStop(status,id);//删除所有Redis缓存clearRedisCache("dish_*");return Result.success();}/*** 清除所有Redis缓存* @return*/private void clearRedisCache(String pattern) {Set keys = redisTemplate.keys(pattern);redisTemplate.delete(keys);}}

示意图:

(4)测试功能

小程序查询菜品分类,如“传统主食”,打开Redis数据库,查看缓存的数据,key为“dish_12”

打开前端网页:菜品管理

找到菜品分类为“传统主食”的“馒头”,点击修改,修改价格,保存

回到Redis数据库,可以发现缓存全没了,成功!

        测试“新增菜品”,清理缓存功能,先出小程序,查询“传统主食”及任意多个菜品分类,打开Redis数据库出现“dish_12”,去前端网页新增菜品,点击保存,最后查看数据库“dish_12”是否还存在,没有则表示成功!

2、缓存套餐

Spring Cache知识点

         SpringCache是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。SpringCache提供了一层抽象,底层可以切换不同的缓存实现,例如:
1、EHCache
2、Caffeine
3、Redis

依赖导入:(源文件已有)

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId><version>2.7.3</version>
</dependency>

 我们的项目使用的是Redis。pom文件导入对应的缓存类型坐标,即可切换不同的缓存实现

如:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>3.0.0</version>
</dependency>

2-2、代码完善

 源文件的pom文件已导入Redis和spring cache依赖

(1)user/SetmealController完善

位置:sky-server/src/main/java/com/sky/controller/user/SetmealController.java

新增代码:

/*** 条件查询** @param categoryId* @return*/
@Cacheable(cacheNames = "setmealList", key = "#categoryId")
@GetMapping("/list")
@ApiOperation("根据分类id查询套餐")
public Result<List<Setmeal>> list(Long categoryId) {Setmeal setmeal = new Setmeal();setmeal.setCategoryId(categoryId);setmeal.setStatus(StatusConstant.ENABLE);List<Setmeal> list = setmealService.list(setmeal);return Result.success(list);
}

完整代码:

package com.sky.controller.user;import com.sky.constant.StatusConstant;
import com.sky.entity.Setmeal;
import com.sky.result.Result;
import com.sky.service.SetmealService;
import com.sky.vo.DishItemVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController("userSetmealController")
@RequestMapping("/user/setmeal")
@Api(tags = "C端-套餐浏览接口")
public class SetmealController {@Autowiredprivate SetmealService setmealService;/*** 条件查询** @param categoryId* @return*/@Cacheable(cacheNames = "setmealList", key = "#categoryId")@GetMapping("/list")@ApiOperation("根据分类id查询套餐")public Result<List<Setmeal>> list(Long categoryId) {Setmeal setmeal = new Setmeal();setmeal.setCategoryId(categoryId);setmeal.setStatus(StatusConstant.ENABLE);List<Setmeal> list = setmealService.list(setmeal);return Result.success(list);}/*** 根据套餐id查询包含的菜品列表** @param id* @return*/@GetMapping("/dish/{id}")@ApiOperation("根据套餐id查询包含的菜品列表")public Result<List<DishItemVO>> dishList(@PathVariable("id") Long id) {List<DishItemVO> list = setmealService.getDishItemById(id);return Result.success(list);}
}

示意图:

注意:爆红是因为导错包,选“org.springframework.cache.annotation.Cacheable;”

(2)admin/SetmealController完善

位置:sky-server/src/main/java/com/sky/controller/admin/SetmealController.java

新增代码:

@CacheEvict(value = "setmealCache", key = "#setmealDTO.categoryId")//精确匹配key,清理缓存
    @CacheEvict(value = "setmealCache", allEntries = true)//清理所有缓存

完整代码:

package com.sky.controller.admin;import com.sky.dto.SetmealDTO;
import com.sky.dto.SetmealPageQueryDTO;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.SetmealService;
import com.sky.vo.SetmealVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.web.bind.annotation.*;import java.util.List;/*** 套餐管理*/
@RestController
@RequestMapping("/admin/setmeal")
@Api(tags = "套餐相关接口")
@Slf4j
public class SetmealController {@Autowiredprivate SetmealService setmealService;/*** 新增套餐* @param setmealDTO* @return*/@CacheEvict(value = "setmealCache", key = "#setmealDTO.categoryId")//精确匹配key,清理缓存@PostMapping@ApiOperation("新增套餐")//由于SetmealDTO中有List<SetmealDish> setmealDishes = new ArrayList<>();,所以这里需要使用@RequestBody注解//使用SetmealDTOwenden @RequestBody注解,将json数据绑定到SetmealDTO对象中public Result save(@RequestBody SetmealDTO setmealDTO) {log.info("新增套餐:{}", setmealDTO);setmealService.saveWithDish(setmealDTO);return Result.success();}/*** 套餐分页查询* @param setmealPageQueryDTO* @return*/@GetMapping("/page")@ApiOperation("分页查询")public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO) {PageResult pageResult = setmealService.pageQuery(setmealPageQueryDTO);return Result.success(pageResult);}/*** 批量删除套餐* @param ids* @return*/@CacheEvict(value = "setmealCache", allEntries = true)@DeleteMapping@ApiOperation("批量删除套餐")public Result delete(@RequestParam List<Long> ids) {log.info("批量删除套餐:{}", ids);setmealService.delete(ids);return Result.success();}/*** 根据id获取套餐* @param id* @return*/@GetMapping("/{id}")@ApiOperation("根据id获取套餐")public Result<SetmealVO> getById(@PathVariable("id") Long id){log.info("根据id获取套餐:{}", id);SetmealVO setmealVO = setmealService.getById(id);return Result.success(setmealVO);}/*** 更新套餐1* @param setmealDTO* @return*/@CacheEvict(value = "setmealCache", allEntries = true)@PutMapping@ApiOperation("更新套餐")public Result Update(@RequestBody SetmealDTO setmealDTO) {log.info("更新套餐:{}", setmealDTO);setmealService.update(setmealDTO);return Result.success();}/*** 套餐起售停售* @param status* @param id* @return*/@CacheEvict(value = "setmealCache", allEntries = true)@PostMapping("/status/{status}")@ApiOperation("套餐起售停售")public Result startOrStop(@PathVariable Integer status, Long id) {setmealService.startOrStop(status, id);return Result.success();}}

(3)功能测试

1、全部清理

打开小程序,任意点击多个菜品分类,查看Redis数据库,打开前端网页,修改菜品信息,保存,查看数据库,若菜品数据全清空,则表示功能完成!

2、精确清除缓存(新增菜品)

与前面一样的操作,只删除对应的dish_?,如dish_12,即算完成!

至此,缓存菜品和缓存套餐功能已完成!


文章转载自:

http://psINflVE.wpqwk.cn
http://PYSdMPmm.wpqwk.cn
http://JXxRowxI.wpqwk.cn
http://OXJXLNyZ.wpqwk.cn
http://OD4gCMLd.wpqwk.cn
http://0Sz6bRb6.wpqwk.cn
http://l77DfY5C.wpqwk.cn
http://A23DcxM7.wpqwk.cn
http://W3Prcbbc.wpqwk.cn
http://mfJELtqx.wpqwk.cn
http://JRqQsENi.wpqwk.cn
http://MXBe2cVm.wpqwk.cn
http://vllMse1i.wpqwk.cn
http://gNVCPgQr.wpqwk.cn
http://Zk3czRfr.wpqwk.cn
http://sm5wi80O.wpqwk.cn
http://DHUcHSeP.wpqwk.cn
http://f2ig0u8a.wpqwk.cn
http://CqOqQFEZ.wpqwk.cn
http://B5IiC4pS.wpqwk.cn
http://XltnuLEa.wpqwk.cn
http://tw3Gtr2I.wpqwk.cn
http://cMg0TRkz.wpqwk.cn
http://5BtXqHIp.wpqwk.cn
http://2zseiHXc.wpqwk.cn
http://TdbW222U.wpqwk.cn
http://phCqwHfx.wpqwk.cn
http://8GfLPqUA.wpqwk.cn
http://1GVZ55tM.wpqwk.cn
http://HVthPA39.wpqwk.cn
http://www.dtcms.com/a/374686.html

相关文章:

  • 51.不可变基础设施:云原生时代的「乐高城堡」建造法
  • Redis小白入门
  • 分层-三层架构
  • 实战:HarmonyOS 中 HEIF 图像开发全流程(图处理篇)
  • 深入 Kubernetes:从零到生产的工程实践与原理洞察
  • 在Ubuntu上修改Nginx的默认端口(例如从80端口改为其他端口,如8080)
  • 《用 Pandas 和 Matplotlib 绘制柱状图:从数据读取到可视化表达的实战指南》
  • python之socket网络编程
  • 【用与非门设计一个七段显示译码器,要求显示Y, E, S 三个符号+门电路符号逻辑式】2022-12-5
  • 解决 Ubuntu 25.04 下 make menuconfig 报 ncurses 错误的问题
  • (49)es容器化部署启动报错-RBAC权限问题
  • MacOS 运行CosyVoice
  • Adam优化算法:深度学习的自适应动量估计方法
  • macos deepctr_torch虚拟环境配置
  • react的filber架构
  • Spring框架事件驱动架构核心注解之@EventListener
  • ARM的big.LITTLE架构
  • 整体设计 之 绪 思维导图引擎 :思维价值链分层评估的 思维引导和提示词导航 之 引 认知系统 之8 之 序 认知元架构 之3(豆包助手 之5)
  • 飞算JavaAI全链路实战:智能构建高可用电商系统核心架构
  • 01-AI-神经网络-视觉-PaddleDetection交通信号灯的目标检测的模型训练(平台提供的数据集)
  • SpringBoot改造MCP服务器(StreamableHTTP)
  • Gradle 与 Android 构建缓存机制全面总结
  • 数据结构题集-第四章-串-采用特定数据类型对串求逆
  • 新能源汽车中维修开关有什么作用?
  • GitHub 热榜项目 - 日榜(2025-09-09)
  • Go 装饰器模式学习文档
  • 20.44 QLoRA调参秘籍:零成本实现7B模型微调,参数黄金配比全解析(附3090实战方案)
  • Ubuntu 22.04 安装 Docker Compose 最新最简单完整指南​
  • 网络原理——传输层协议UDP
  • 从Java全栈开发到云原生实践:一次真实面试的深度剖析