news 2026/2/12 4:58:30

学生党学习笔记——黑马程序员Java项目实战《苍穹外卖》——Day07 数据缓存购物车操作

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
学生党学习笔记——黑马程序员Java项目实战《苍穹外卖》——Day07 数据缓存购物车操作

前言

今天的内容主要是访问量提升对数据库访问性能影响问题的解决 ——数据缓存。其中spring cache框架提供底层redis的实现。

  • 缓存菜品

  • 缓存套餐

  • 添加购物车

  • 查看购物车

  • 清空购物车

《苍穹外卖day06》:学生党学习笔记——黑马程序员Java项目实战《苍穹外卖》——Day06 HttpClient&微信登录-CSDN博客


视频学习链接黑马程序员Java项目实战《苍穹外卖》,最适合新手的SpringBoot+SSM的企业级Java项目实战_哔哩哔哩_bilibili
资料网盘链接1、黑马程序员Java项目《苍穹外卖》企业级开发实战_免费高速下载|百度网盘-分享无限制
本人项目远程仓库sky-take-out: 黑马程序员Java项目实战《苍穹外卖》,最适合新手的SpringBoot+SSM的企业级Java项目实战


功能实现效果图:


缓存菜品

问题说明

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

结果:系统响应慢、用户体验差

实现思路

通过Redis来缓存菜品数据,减少数据库查询操作。在查询数据库前根据内存中是否缓存数据判断是否访问数据库,从而减少数据库压力。

缓存逻辑分析

- 每个分类下的菜品保存一份缓存数据
- 数据库中菜品数据有变更时清理缓存数据


代码开发

修改用户端接口DishController 的 list方法,加入缓存处理逻辑:

@Autowired private RedisTemplate redisTemplate; /** * 根据分类id查询菜品 * * @param categoryId * @return */ @GetMapping("/list") @ApiOperation("根据分类id查询菜品") public Result<List<DishVO>> list(Long categoryId) { //构造redis中的key,规则:dish_分类id String key = "dish_" + categoryId; //查询redis中是否存在菜品数据 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);//查询起售中的菜品 //如果不存在,查询数据库,将查询到的数据放入redis中 list = dishService.listWithFlavor(dish); //////////////////////////////////////////////////////// redisTemplate.opsForValue().set(key, list); return Result.success(list); }

为了保证数据库Redis中的数据保持一致,修改管理端接口 DishController的相关方法,加入清理缓存逻辑。

需要改造的方法:

- 新增菜品
- 修改菜品
- 批量删除菜品
- 起售、停售菜品


抽取清理缓存的方法

管理端DishController中添加

注意:java中操作redis删除key的delete方法不能用*匹配所以需要先用keys方法匹配要删除的key再删除

@Autowired private RedisTemplate redisTemplate; /** * 清理缓存数据 * @param pattern */ private void cleanCache(String pattern){ Set keys = redisTemplate.keys(pattern); redisTemplate.delete(keys); }

调用清理缓存的方法,保证数据一致性:

/** * 新增菜品 * * @param dishDTO * @return */ @PostMapping @ApiOperation("新增菜品") public Result save(@RequestBody DishDTO dishDTO) { log.info("新增菜品:{}", dishDTO); dishService.saveWithFlavor(dishDTO); //清理缓存数据 String key = "dish_" + dishDTO.getCategoryId(); cleanCache(key); return Result.success(); } /** * 菜品批量删除 * * @param ids * @return */ @DeleteMapping @ApiOperation("菜品批量删除") public Result delete(@RequestParam List<Long> ids) { log.info("菜品批量删除:{}", ids); dishService.deleteBatch(ids); //将所有的菜品缓存数据清理掉,所有以dish_开头的key cleanCache("dish_*"); return Result.success(); } /** * 修改菜品 * * @param dishDTO * @return */ @PutMapping @ApiOperation("修改菜品") public Result update(@RequestBody DishDTO dishDTO) { log.info("修改菜品:{}", dishDTO); dishService.updateWithFlavor(dishDTO); //将所有的菜品缓存数据清理掉,所有以dish_开头的key cleanCache("dish_*"); return Result.success(); } /** * 菜品起售停售 * * @param status * @param id * @return */ @PostMapping("/status/{status}") @ApiOperation("菜品起售停售") public Result<String> startOrStop(@PathVariable Integer status, Long id) { dishService.startOrStop(status, id); //将所有的菜品缓存数据清理掉,所有以dish_开头的key cleanCache("dish_*"); return Result.success(); }

缓存套餐


Spring Cache

介绍

Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。

Spring Cache 提供了一层抽象,底层可以切换不同的缓存实现,例如:

- EHCache
- Caffeine
-Redis(常用)

<!-- 别忘了redis坐标哦~ --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> <version>2.7.3</version> </dependency>

常用注解

在SpringCache中提供了很多缓存操作的注解,常见的是以下的几个:

注解说明
@EnableCaching开启缓存注解功能,通常加在启动类
@Cacheable在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法返回值放到缓存中
@CachePut将方法的返回值放到缓存中
@CacheEvict一条多条数据从缓存中删除

spring boot项目中,使用缓存技术只需在项目中导入相关缓存技术的依赖包,并在启动类上使用@EnableCaching开启缓存支持即可。例如,使用Redis作为缓存技术,只需要导入Spring data Redis的maven坐标即可。


实现思路

实现步骤:

1). 导入Spring Cache和Redis相关maven坐标(项目已导入)

2). 在启动类上加入@EnableCaching注解,开启缓存注解功能

3). 在用户端接口SetmealController的 list 方法上加入@Cacheable注解

4). 在管理端接口SetmealController的 save、delete、update、startOrStop等(数据库增删改)方法上加入CacheEvict注解


代码开发

在用户端接口SetmealControllerlist方法上加入@Cacheable注解

cacheNames:key前缀名 key:动态生成,支持spel

/** * 条件查询 * * @param categoryId * @return */ @GetMapping("/list") @ApiOperation("根据分类id查询套餐") @Cacheable(cacheNames = "setmealCache",key = "#categoryId") //key: setmealCache::100 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); }

在管理端接口SetmealController的 save、delete、update、startOrStop等方法上加入CacheEvict注解

/** * 新增套餐 * * @param setmealDTO * @return */ @PostMapping @ApiOperation("新增套餐") @CacheEvict(cacheNames = "setmealCache",key = "#setmealDTO.categoryId")//key: setmealCache::100 public Result save(@RequestBody SetmealDTO setmealDTO) { setmealService.saveWithDish(setmealDTO); return Result.success(); } /** * 批量删除套餐 * * @param ids * @return */ @DeleteMapping @ApiOperation("批量删除套餐") @CacheEvict(cacheNames = "setmealCache",allEntries = true) public Result delete(@RequestParam List<Long> ids) { setmealService.deleteBatch(ids); return Result.success(); } /** * 修改套餐 * * @param setmealDTO * @return */ @PutMapping @ApiOperation("修改套餐") @CacheEvict(cacheNames = "setmealCache",allEntries = true) public Result update(@RequestBody SetmealDTO setmealDTO) { setmealService.update(setmealDTO); return Result.success(); } /** * 套餐起售停售 * * @param status * @param id * @return */ @PostMapping("/status/{status}") @ApiOperation("套餐起售停售") @CacheEvict(cacheNames = "setmealCache",allEntries = true) public Result startOrStop(@PathVariable Integer status, Long id) { setmealService.startOrStop(status, id); return Result.success(); }

添加购物车

需求分析和设计

产品原型

用户可以将菜品或者套餐添加到购物车。对于菜品来说,如果设置了口味信息,则需要选择规格后才能加入购物车;对于套餐来说,可以直接点击将当前套餐加入购物车。在购物车中可以修改菜品和套餐的数量,也可以清空购物车

接口设计

说明:添加购物车时,有可能添加菜品,也有可能添加套餐。故传入参数要么是菜品id,要么是套餐id。


代码开发

//Controller层 /** * 购物车 */ @RestController @RequestMapping("/user/shoppingCart") @Slf4j @Api(tags = "C端-购物车接口") public class ShoppingCartController { @Autowired private ShoppingCartService shoppingCartService; /** * 添加购物车 * @param shoppingCartDTO * @return */ @PostMapping("/add") @ApiOperation("添加购物车") public Result<String> add(@RequestBody ShoppingCartDTO shoppingCartDTO){ log.info("添加购物车:{}", shoppingCartDTO); shoppingCartService.addShoppingCart(shoppingCartDTO); return Result.success(); } } //Service public interface ShoppingCartService { /** * 添加购物车 * @param shoppingCartDTO */ void addShoppingCart(ShoppingCartDTO shoppingCartDTO); } //Service实现类 @Service public class ShoppingCartServiceImpl implements ShoppingCartService { @Autowired private ShoppingCartMapper shoppingCartMapper; @Autowired private DishMapper dishMapper; @Autowired private SetmealMapper setmealMapper; /** * 添加购物车 * * @param shoppingCartDTO */ public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) { ShoppingCart shoppingCart = new ShoppingCart(); BeanUtils.copyProperties(shoppingCartDTO, shoppingCart); //只能查询自己的购物车数据 shoppingCart.setUserId(BaseContext.getCurrentId()); //判断当前商品是否在购物车中 List<ShoppingCart> shoppingCartList = shoppingCartMapper.list(shoppingCart); if (shoppingCartList != null && shoppingCartList.size() == 1) { //如果已经存在,就更新数量,数量加1 shoppingCart = shoppingCartList.get(0); shoppingCart.setNumber(shoppingCart.getNumber() + 1); shoppingCartMapper.updateNumberById(shoppingCart); } else { //如果不存在,插入数据,数量就是1 //判断当前添加到购物车的是菜品还是套餐;为购物车其余字段赋值 Long dishId = shoppingCartDTO.getDishId(); if (dishId != null) { //添加到购物车的是菜品 Dish dish = dishMapper.getById(dishId); shoppingCart.setName(dish.getName()); shoppingCart.setImage(dish.getImage()); shoppingCart.setAmount(dish.getPrice()); } else { //添加到购物车的是套餐 Setmeal setmeal = setmealMapper.getById(shoppingCartDTO.getSetmealId()); shoppingCart.setName(setmeal.getName()); shoppingCart.setImage(setmeal.getImage()); shoppingCart.setAmount(setmeal.getPrice()); } shoppingCart.setNumber(1); shoppingCart.setCreateTime(LocalDateTime.now()); shoppingCartMapper.insert(shoppingCart); } } } //Mapper @Mapper public interface ShoppingCartMapper { /** * 条件查询 * * @param shoppingCart * @return */ List<ShoppingCart> list(ShoppingCart shoppingCart); /** * 更新商品数量 * * @param shoppingCart */ @Update("update shopping_cart set number = #{number} where id = #{id}") void updateNumberById(ShoppingCart shoppingCart); /** * 插入购物车数据 * * @param shoppingCart */ @Insert("insert into shopping_cart (name, user_id, dish_id, setmeal_id, dish_flavor, number, amount, image, create_time) " + " values (#{name},#{userId},#{dishId},#{setmealId},#{dishFlavor},#{number},#{amount},#{image},#{createTime})") void insert(ShoppingCart shoppingCart); }
<mapper namespace="com.sky.mapper.ShoppingCartMapper"> <select id="list" parameterType="ShoppingCart" resultType="ShoppingCart"> select * from shopping_cart <where> <if test="userId != null"> and user_id = #{userId} </if> <if test="dishId != null"> and dish_id = #{dishId} </if> <if test="setmealId != null"> and setmeal_id = #{setmealId} </if> <if test="dishFlavor != null"> and dish_flavor = #{dishFlavor} </if> </where> order by create_time desc </select> </mapper>

查看购物车

需求分析和设计

产品原型

当用户添加完菜品和套餐后,可进入到购物车中,查看购物中的菜品和套餐。

接口设计


代码开发

/** Controller * 查看购物车 * @return */ @GetMapping("/list") @ApiOperation("查看购物车") public Result<List<ShoppingCart>> list(){ return Result.success(shoppingCartService.showShoppingCart()); } /** service * 查看购物车 * @return */ List<ShoppingCart> showShoppingCart(); /** service实现类 * 查看购物车 * @return */ public List<ShoppingCart> showShoppingCart() { return shoppingCartMapper.list(ShoppingCart. builder(). userId(BaseContext.getCurrentId()). build()); }

清空购物车

当点击清空按钮时,会把购物车中的数据全部清空。

接口设计


代码开发

/** Controller * 清空购物车商品 * @return */ @DeleteMapping("/clean") @ApiOperation("清空购物车商品") public Result<String> clean(){ shoppingCartService.cleanShoppingCart(); return Result.success(); } /** Service * 清空购物车商品 */ void cleanShoppingCart(); /** service实现类 * 清空购物车商品 */ public void cleanShoppingCart() { shoppingCartMapper.deleteByUserId(BaseContext.getCurrentId()); } /** mapper * 根据用户id删除购物车数据 * * @param userId */ @Delete("delete from shopping_cart where user_id = #{userId}") void deleteByUserId(Long userId);
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/7 13:04:10

大模型开发实战:从零实现 MCP Server,吃透模型上下文协议核心原理

前言 大家好呀&#xff5e; 我是菲菲&#xff0c;一名刚入门大模型开发的学妹&#xff01;在跟着教程探索 Agent 与工具协作的过程中&#xff0c;“MCP 协议” 这个词频繁出现 —— 官网白皮书看着清晰易懂&#xff0c;可真要动手实操时&#xff0c;却总陷入 “一看就会、一写就…

作者头像 李华
网站建设 2026/2/8 12:58:04

ComfyUI插件整合vLLM,图像生成延迟下降70%

ComfyUI插件整合vLLM&#xff0c;图像生成延迟下降70% 在AIGC创作流程中&#xff0c;一个常见的痛点是&#xff1a;用户输入“赛博朋克城市”这样的关键词后&#xff0c;等待系统生成高质量图像的时间往往长达秒级。这不仅打断了创作节奏&#xff0c;更在高并发场景下导致服务响…

作者头像 李华
网站建设 2026/2/10 16:00:15

[N_122]基于springboot,vue网上订餐系统

开发工具&#xff1a;IDEA 服务器&#xff1a;Tomcat9.0&#xff0c; jdk1.8 项目构建&#xff1a;maven 数据库&#xff1a;mysql5.7 前端技术 &#xff1a;VueElementUI 服务端技术&#xff1a;springbootmybatisredis 本系统分用户前台和管理后台两部分&#xff0c;…

作者头像 李华
网站建设 2026/2/3 8:41:20

零基础创业首选!AI一键抠图换底的全自动证件照系统源码

温馨提示&#xff1a;文末有资源获取方式一套功能完备的AI智能证件照在线制作系统源码&#xff0c;正是这样一个“拎包入住”式的创业解决方案。它省去了复杂的研发过程&#xff0c;直接将经过市场验证的“赚钱机器”交到你手中。源码获取方式在源码闪购网。系统提供的完整功能…

作者头像 李华