news 2026/1/13 23:23:50

学生党学习笔记——黑马程序员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/1/9 10:13:04

管家婆辉煌软件账套开账前需要录入哪些信息

在软件正式使用前我们还有一个重要的步骤&#xff0c;那就是建立基本信息并且录入期初数据&#xff0c;为什么要录入期初呢&#xff0c;如果在使用软件前我们是使用手工帐去记录的&#xff0c;那么就会存在我们和供应商之前的欠款或者是使用软件前我已经购买了商品&#xff0c;…

作者头像 李华
网站建设 2026/1/12 21:36:11

绕过 Web 应用程序防火墙 (WAF) 的 5 种方法

WAF 是一种网络安全解决方案&#xff0c;用于过滤和阻止恶意网络流量。常见的供应商包括 CloudFlare、AWS、Citrix、Akamai、Radware、Microsoft Azure 和 Barracuda。 根据防火墙使用的机制组合&#xff0c;绕过方法可能会有所不同。例如&#xff0c;WAF 可能使用正则表达式来…

作者头像 李华
网站建设 2026/1/12 6:27:39

中国AI创新被低估了吗?

原问题&#xff1a;你如何看待DeepMind CEO说「中国AI毫无创新但跟进速度可怕」&#xff1f;国外大多只愿意以从上往下的姿态&#xff0c;讲它逻辑内的“创新”&#xff0c;国内系统产业生态性的创新&#xff0c;国外又故意“看不见”&#xff0c;制造业、化工业、数据链&#…

作者头像 李华
网站建设 2026/1/12 10:40:56

【数据操作与可视化】Serborn绘图-类别散点图和热力图

【数据操作与可视化】Serborn绘图-类别散点图和热力图 一、类别散点图 通过 stripplot()函数可以画一个散点图&#xff0c; stripplot0函数的语法格式如下。 seaborn.stripplot(xNone, yNone, hueNone, dataNone, orderNone, hue_orderNone, jitterFalse)上述函数中常用参数的含…

作者头像 李华
网站建设 2026/1/13 17:22:35

PDF文本提取的“杀手锏”!DeepSeek-OCR+Python,让表格、段落分毫不差!

前言 这一期原本是计划在 DeepSeek-OCR 前段刚火爆全网时&#xff0c;给大家分享下使用心得&#xff0c;无奈这段时间事情太多&#xff0c;耽误了更新进度&#xff0c;现在出这期详细体验还不算太晚吧。 之前我在这个账号里分享了很多期有关 OCR 识别的内容&#xff0c;是因为…

作者头像 李华