苍穹外卖07(缓存菜品,SpringCache,缓存套餐,添加购物车菜品和套餐多下单,查看购物车,清除购物车,删除购物车中一个商品)
目录
一、缓存菜品
1 问题说明
2 实现思路
3 代码开发:修改DishServiceImpl
4 功能测试
二、SpringCache
1. 介绍
2. 使用语法
1 起步依赖
2 使用要求
3 常用注解
4 SpEL表达式(了解备用)
5 步骤小结
3.入门案例
1 准备环境
2 使用入门
1 引导类上加@EnableCaching
2 更新缓存加@CachePut
@CachePut 说明
使用示例
3 使用缓存加@Cacheable
测试效果
4 清理缓存加@CacheEvict
4. 使用小结
三、缓存套餐
1 问题说明
2 实现思路
3 代码开发
1 添加依赖坐标
2 修改引导类加@EnableCaching
3 修改SetmealServiceImpl
浏览套餐时使用缓存
套餐变更时清理缓存
4 功能测试
四、添加购物车
1. 需求分析和设计
1 产品原型
2 接口设计
3 表设计
2. 代码开发
1 DTO设计
2 ShoppingCartController
3 ShoppingCartService
4 ShoppingCartServiceImpl
5 ShoppingCartMapper
6 ShoppingCartMapper.xml
3. 功能测试
五、查看购物车
1. 需求分析和设计
1 产品原型
2 接口设计
2. 代码开发
1 ShoppingCartController
2 ShoppingCartService
3 ShoppingCartServiceImpl
4 ShoppingCartMapper
3. 功能测试
六、清空购物车
1. 需求分析和设计
1 产品原型
2 接口设计
2. 代码开发
1 ShoppingCartController
2 ShoppingCartService
3 ShoppingCartServiceImpl
4 ShoppingCartMapper
3. 功能测试
七、删除购物车中一个商品
一、缓存菜品
1 问题说明
用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大。
2 实现思路
通过Redis来缓存菜品数据,减少数据库查询操作。
缓存逻辑分析:
-
每个分类下的菜品保存一份缓存数据
-
数据库中菜品数据有变更时清理缓存数据
要做的事情:使用缓存优化菜品相关的性能
-
给用户端的功能增加缓存:查询菜品时优先使用缓存
-
管理端功能增删改时要清理缓存:新增、修改、删除、上下架操作都要清理缓存
3 代码开发:修改DishServiceImpl
修改queryUserDishesByCategoryId方法,增加缓存
@Autowired
private StringRedisTemplate redisTemplate;@Override
public List<DishVO> queryUserDishesByCategoryId(Long categoryId) {//=========↓↓↓↓↓增加代码:先从Redis中查询缓存 start↓↓↓↓↓=========String cacheKey = "dish:" + categoryId;String dishesJson = redisTemplate.opsForValue().get(cacheKey);if (dishesJson != null && !"".equals(dishesJson)) {return JSON.parseArray(dishesJson, DishVO.class);}//=========↑↑↑↑↑增加代码:先从Redis中查询缓存 end↑↑↑↑↑=========//1. 查询菜品列表,及每个菜品关联的分类名称List<DishVO> dishVOList = dishMapper.selectEnableListByCategoryId(categoryId);//2. 查询每个菜品关联的口味列表for (DishVO dishVO : dishVOList) {//查询口味列表List<DishFlavor> dishFlavors = dishFlavorMapper.selectListByDishId(dishVO.getId());dishVO.setFlavors(dishFlavors);}//=========↓↓↓↓↓增加代码:把数据缓存到Redis里 start↓↓↓↓↓=========redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(dishVOList));//=========↑↑↑↑↑增加代码:把数据缓存到Redis里 end↑↑↑↑↑========= return dishVOList;
}
菜品变化时清理缓存
为了保证数据库和Redis中的数据保持一致,修改管理端接口 DishController 的相关方法,加入清理缓存逻辑。
需要改造的方法:
-
新增菜品
-
修改菜品
-
批量删除菜品
-
起售、停售菜品
新增菜品时清理缓存
修改方法,增加清理缓存的代码
@Override
@Transactional
public Result addDish(DishDTO dto) {//1. 把菜品信息存储到dish表里Dish dish = new Dish();BeanUtils.copyProperties(dto, dish);dishMapper.insert(dish);//2. 把菜品关联的口味保存到dish_flavors表里:每个dishFlavor都要使用到dish的idList<DishFlavor> flavors = dto.getFlavors();if (flavors != null && flavors.size() > 0) {//把菜品的id,设置给每个DishFlavor对象for (DishFlavor flavor : flavors) {flavor.setDishId(dish.getId());}// flavors.forEach(dishFlavor -> dishFlavor.setDishId(dish.getId()));//把DishFlavor对象的集合保存到数据库里,才有dishId值// INSERT INTO dish_flavor (dish_id, name, value) VALUES (菜品id,?,?),(),(),(),...;dishFlavorMapper.batchInsert(flavors);}//=======↓↓↓↓↓增加代码:清理缓存↓↓↓↓↓=======redisTemplate.delete("dish:" + dto.getCategoryId());//=======↑↑↑↑↑增加代码:清理缓存↑↑↑↑↑=======return Result.success();
}
删除菜品时清理缓存
修改方法,增加清理缓存的代码
@Override
@Transactional
public Result batchDeleteByIds(List<Long> ids) {//如果有某个菜品,状态是“起售”的,就抛异常,不允许删除int count = dishMapper.selectEnableDishCountByIds(ids);if (count > 0) {throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);}//如果有某个菜品,关联了套餐,就抛异常,不允许删除count = setmeatlDishMapper.selectCountByDishIds(ids);if (count > 0) {throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);}//删除这些菜品dishMapper.batchDeleteByIds(ids);//删除这些菜品对应口味列表dishFlavorMapper.batchDeleteByDishIds(ids);//=======↓↓↓↓↓增加代码:清理缓存↓↓↓↓↓=======Set<String> keys = redisTemplate.keys("dish:*");redisTemplate.delete(keys);//=======↑↑↑↑↑增加代码:清理缓存↑↑↑↑↑=======return Result.success();
}
修改菜品时清理缓存
修改方法,增加清理缓存的代码
@Override
@Transactional
public Result updateDishById(DishDTO dto) {//1. 先修改菜品信息Dish dish = new Dish();BeanUtils.copyProperties(dto, dish);dishMapper.updateById(dish);//2. 删除菜品关联的口味列表:创建一个集合,只放一个元素进去,怎么实现?Collections.singletonList(元素值)dishFlavorMapper.batchDeleteByDishIds(Collections.singletonList(dto.getId()));//3. 把客户端提交的口味列表,重新添加到数据库表List<DishFlavor> flavors = dto.getFlavors();for (DishFlavor flavor : flavors) {flavor.setDishId(dish.getId());}dishFlavorMapper.batchInsert(flavors);//=======↓↓↓↓↓增加代码:清理缓存↓↓↓↓↓=======redisTemplate.delete("dish:" + dto.getCategoryId());//=======↑↑↑↑↑增加代码:清理缓存↑↑↑↑↑=======return Result.success();
}
4 功能测试
可以通过如下方式进行测试:
-
查看控制台sql
-
前后端联调
-
查看Redis中的缓存数据
浏览菜品时使用缓存
以加入缓存、菜品修改两个功能测试为例,通过前后端联调方式,查看控制台sql的打印和Redis中的缓存数据变化。
当第一次查询某个分类的菜品时,会从数据为中进行查询,同时将查询的结果存储到Redis中,在后绪的访问,若查询相同分类的菜品时,直接从Redis缓存中查询,不再查询数据库。
登录小程序:选择某一个菜品分类,例如蜀味牛蛙(id=17)。
-
第一次点击访问:
-
服务端控制台里输出了SQL语句,说明执行了SQL语句,是从数据库里查询的
-
查看Redis里有 dish:17 对应的菜品数据,说明数据已经被缓存到Redis里了
-
-
刷新小程序,清理一下idea的控制台
-
再次点击访问“蜀味牛蛙”:
-
服务端控制台里没有输出SQL语句,说明没有执行SQL,是从Redis里查询的缓存
-
二、SpringCache
1. 介绍
在企业开发中,缓存对于提升程序性能有非常大的作用,所以已经广泛应用于企业项目开发中。但是缓存技术是多种多样的,例如Redis、Caffeine、MemCache、EhCache等等。而不同的缓存技术,其操作方法并不统一,这就对于开发人员使用缓存造成了一些障碍。
从Spring3.1版本开始,Spring就利用AOP思想,对不同的缓存技术做了再封装,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。让开发人员只专注于业务,不需要再关心具体的缓存技术。
2. 使用语法
1 起步依赖
起步依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>2.7.3</version>
</dependency>
如果只添加上述一个依赖的话,springCache默认将会使用ConcurrentHashMap作为缓存容器。但是Spring Cache 其实提供了一层抽象,底层可以切换不同的缓存实现,例如:
-
EHCache,如果添加了EHCache的依赖坐标,SpringCache将会使用EhCache作为缓存容器
-
Caffeine,如果添加了caffeine的依赖坐标,SpringCache将会使用Caffeine作为缓存容器
-
Redis(常用),如果添加了Redis依赖坐标,SpringCache将会使用Redis作为缓存容器
所以实际开发中,通常是添加两个坐标:
<!-- SpringCache起步依赖坐标 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Redis起步依赖坐标 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2 使用要求
SpringCache结合Redis时,默认会使用JDK序列化方式,将数据序列化成字节数组,再缓存起来。
我们用的就是这种方式,所以我们的实体类要实现Serializable
接口
3 常用注解
在SpringCache中提供了很多缓存操作的注解,常见的是以下的几个:
在spring boot项目中,使用缓存技术只需在项目中导入相关缓存技术的依赖包,并在启动类上使用@EnableCaching开启缓存支持即可。
例如,使用Redis作为缓存技术,只需要导入Spring data Redis的maven坐标即可。
4 SpEL表达式(了解备用)
Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:
注意:
-
使用方法参数时,可以直接写成
#参数名
,也可以写成:#p参数索引
,例如#p0
表示索引0的参数
5 步骤小结
-
先添加依赖坐标
-
修改引导类,加注解@EnableCaching,开启缓存功能
-
哪个方法,查询数据时想要优先查缓存,就在方法上加注解:@Cacheable
-
哪个方法,执行后想要更新缓存的数据,就在方法上加注解:@CachePut
-
哪个方法,执行后想要清理缓存的数据,就在方法上加注解:@CacheEvict
3.入门案例
1 准备环境
导入基础工程:底层已使用Redis缓存实现
基础环境的代码,在我们今天的资料中已经准备好了, 大家只需要将这个工程导入进来就可以了
数据库准备:
CREATE DATABASE spring_cache_demo;
use database spring_cache_demo;
create table user
(
id bigint auto_increment primary key,
name varchar(45) null,
age int null
);
2 使用入门
1 引导类上加@EnableCaching
引导类上加@EnableCaching:
package com.itheima;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;@EnableCaching //开启声明式缓存功能
@SpringBootApplication
public class CacheDemoApplication {public static void main(String[] args) {SpringApplication.run(CacheDemoApplication.class, args);}
}
2 更新缓存加@CachePut
@CachePut 说明
-
作用:将方法返回值,放入缓存(更新缓存)
-
用法:
@CachePut(cacheNames="", key="")
-
缓存的key:Spring将使用
cacheNames的值::key的值
作为缓存的key -
缓存的值:Spring将方法的返回值作为缓存的value
-
-
注意:注解里的
key
支持SpringEL表达式
使用示例
如果在做新增操作,或者修改操作时,可以更新缓存:当新增或修改操作后,希望把最新的数据缓存起来,方便后续使用。可以在新增或修改方法上加注解@CachePut
/*** 新增用户方法* 注解@CachePut将会把方法返回值缓存起来:以cacheNames+key作为缓存的key,以方法返回值作为缓存的value*/
@Override
@CachePut(cacheNames = "user", key = "#user.id")
public User addUser(User user) {userMapper.insert(user);return user;
}
说明:key的写法如下
#user.id : #user指的是方法形参的名称, id指的是user的id属性 , 也就是使用user的id属性作为key ;
#result.id : #result代表方法返回值,该表达式 代表以返回对象的id属性作为key ;
#p0.id:#p0指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key ;
#a0.id:#a0指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key ;
#root.args[0].id:#root.args[0]指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数
的id属性作为key ;
package com.itheima;import com.itheima.entity.User;
import com.itheima.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
public class CacheTest {@Autowiredprivate UserService userService;@Testpublic void testCachePut(){User user = new User();user.setName("pony");user.setAge(60);//新增完成后,数据库里会多一条数据,使用AnotherRedisDesktopManager连接Redis,会发现也有此用户的缓存userService.addUser(user);}
}
3 使用缓存加@Cacheable
@Cacheable 说明:
-
作用:在方法执行前,spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;
若没有数据,调用方法并将方法返回值放到缓存中
-
用法:
@Cacheable(cacheNames="", key="")
缓存的key:以
cacheNames::key
的值作为key,查找对应的值 -
注意:注解里的
key
支持SpringEL表达式
使用示例
在getById上加注解@Cacheable
/*** 根据id查询用户* Spring会优先从缓存中查找:以cacheNames::key作为key,查找对应的值* 如果找到了,就会直接返回结果;这个方法是不会执行的* 如果找不到,才会执行这个方法,并把方法的返回值缓存起来*/
@Override
@Cacheable(cacheNames = "user", key = "#id")
public User queryById(Long id) {System.out.println(">>>>UserServiceImpl.queryById方法执行了");return userMapper.selectById(id);
}
测试效果
打开Another Redis Desktop Manager,先把用户1的缓存清除掉
然后在测试类里增加测试方法,并执行:
@Test
public void testCacheable(){System.out.println("--------第一次查询用户1,没有缓存,会执行目标方法查询数据库");System.out.println(userService.queryById(1L));System.out.println("--------第二次查询用户1,有缓存了,直接取缓存数据,不会执行这个方法");System.out.println(userService.queryById(1L));System.out.println("--------第三次查询用户1,有缓存了,直接取缓存数据,不会执行这个方法");System.out.println(userService.queryById(1L));
}
4 清理缓存加@CacheEvict
@CacheEvict 说明
-
作用:清理指定缓存
-
用法:
-
用法1:
CacheEvict(cacheNames="", key="")
,清除cacheNames::key
对应的缓存 -
用法2:
CacheEvict(cacheNames="", allEntries=true)
,清理所有以cacheNames::
开头的key对应的缓存
-
-
注意:注解里的
key
支持SpringEL表达式
使用示例
@Override
@CacheEvict(cacheNames = "user", key = "#id")
public void deleteUser(Long id) {System.out.println(">>>>UserServiceImpl.deleteUser方法执行了");userMapper.deleteById(id);
}
在测试类里增加方法并执行:
@Override
@CacheEvict(cacheNames = "user", key = "#id")
public void deleteUser(Long id) {System.out.println(">>>>UserServiceImpl.deleteUser方法执行了");userMapper.deleteById(id);
}
4. 使用小结
-
添加依赖坐标
-
修改引导类,加
@EnableCaching
-
方法上加注解
-
查询方法时,优先查询缓存:在方法上加
@Cacheable(cacheNames="", key="")
Spring优先找缓存的数据,以
cacheNames::key
作为键,去查找缓存;如果找到缓存,就直接返回结果;被调用的这个方法,是不执行的
如果没有缓存:Spring会调用执行这个方法,把方法的返回值 序列化成字节数组,然后缓存起来。
-
调用一个方法时,想要更新缓存数据:在方法上加
@CachePut(cacheNames="", key="")
Spring会先调用方法,然后把方法的返回值序列化成字节数组
以
cacheNames::key
作为键,以返回值的序列化结果 作为值,缓存起来 -
调用一个方法时,想要清理缓存数据:
-
在方法上加
@CacheEvict(cacheNames="", key="")
Spring会先调用方法,再以
cacheNames::key
为键清理掉对应的缓存 -
在方法上加
@CacheEvict(cacheNames="", allEntries=true)
Spring会先调用方法,再删除所有 以
cacheNames
开头的key
-
-
三、缓存套餐
1 问题说明
同缓存菜品一样,我们希望将菜品也缓存到Redis里。这样如果客户端(C端)访问量大了,直接从Redis里读取缓存数据,从而减轻数据库的压力。
2 实现思路
实现步骤:
1). 导入Spring Cache和Redis相关maven坐标
2). 在启动类上加入@EnableCaching
注解,开启缓存注解功能
3). 在SetmealServiceImpl的查询套餐
(用户端)方法上加入@Cacheable注解
4). 在SetmealServiceImpl的 新增套餐
、删除套餐
、修改套餐
、起用套餐
等管理端方法上加入CacheEvict注解
3 代码开发
1 添加依赖坐标
修改sky-server的pom.xml文件,添加依赖坐标:已添加过了,不要重复添加
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2 修改引导类加@EnableCaching
在启动类上加入@EnableCaching注解,开启缓存注解功能
package com.sky;import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.transaction.annotation.EnableTransactionManagement;@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
@EnableCaching
public class SkyApplication {public static void main(String[] args) {SpringApplication.run(SkyApplication.class, args);log.info("server started");}
}
3 修改SetmealServiceImpl
浏览套餐时使用缓存
修改list方法,增加注解@Cacheable
@Override
@Cacheable(cacheNames = "setmeal", key = "#categoryId")
public List<Setmeal> list(Integer categoryId) {return setmealMapper.selectEnableListByCategoryId(categoryId);
}
套餐变更时清理缓存
4 功能测试
通过前后端联调方式来进行测试,同时观察redis中缓存的套餐数据。和缓存菜品功能测试基本一致,不再赘述。
四、添加购物车
1. 需求分析和设计
1 产品原型
用户可以将菜品或者套餐添加到购物车。对于菜品来说,如果设置了口味信息,则需要选择规格后才能加入购物车;对于套餐来说,可以直接点击
将当前套餐加入购物车。在购物车中可以修改菜品和套餐的数量,也可以清空购物车。
2 接口设计
通过上述原型图,设计出对应的添加购物车接口。
说明:添加购物车时,有可能添加菜品,也有可能添加套餐。故传入参数要么是菜品id,要么是套餐id。
3 表设计
用户的购物车数据,也是需要保存在数据库中的,购物车对应的数据表为shopping_cart表,具体表结构如下:
说明:
-
购物车数据是关联用户的,在表结构中,我们需要记录,每一个用户的购物车数据是哪些
-
菜品列表展示出来的既有套餐,又有菜品,如果用户选择的是套餐,就保存套餐ID(setmeal_id),如果用户选择的是菜品,就保存菜品ID(dish_id)
-
对同一个菜品/套餐,如果选择多份不需要添加多条记录,增加数量number即可
2. 代码开发
1 DTO设计
根据添加购物车接口的参数设计DTO:
在sky-pojo模块,ShoppingCartDTO.java已定义
package com.sky.dto;import lombok.Data;
import java.io.Serializable;@Data
public class ShoppingCartDTO implements Serializable {private Long dishId;private Long setmealId;private String dishFlavor;}
2 ShoppingCartController
创建 ShoppingCartController
类
package com.sky.controller.user;import com.sky.dto.ShoppingCartDTO;
import com.sky.result.Result;
import com.sky.service.ShoppingCartService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;@RestController
@Api(tags = "购物车相关接口-C端")
@RequestMapping("/user/shoppingCart")
public class ShoppingCartController {@Autowiredprivate ShoppingCartService cartService;@PostMapping("/add")@ApiOperation("添加购物车")public Result addCart(@RequestBody ShoppingCartDTO dto){return cartService.addCart(dto);}
}
3 ShoppingCartService
创建ShoppingCartService接口:
package com.sky.service;import com.sky.dto.ShoppingCartDTO;
import com.sky.entity.ShoppingCart;
import com.sky.result.Result;public interface ShoppingCartService {/*** 添加购物车* @param dto* @return*/Result addCart(ShoppingCartDTO dto);
}
4 ShoppingCartServiceImpl
创建ShoppingCartServiceImpl实现类
package com.sky.service.impl;import com.sky.context.BaseContext;
import com.sky.dto.ShoppingCartDTO;
import com.sky.entity.Dish;
import com.sky.entity.Setmeal;
import com.sky.entity.ShoppingCart;
import com.sky.mapper.DishMapper;
import com.sky.mapper.SetmealMapper;
import com.sky.mapper.ShoppingCartMapper;
import com.sky.result.Result;
import com.sky.service.ShoppingCartService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.time.LocalDateTime;
import java.util.List;@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {@Autowiredprivate ShoppingCartMapper shoppingCartMapper;@Autowiredprivate DishMapper dishMapper;@Autowiredprivate SetmealMapper setmealMapper;@Overridepublic Result addCart(ShoppingCartDTO dto) {//查询当前商品是否在购物一中Long currentUser = BaseContext.getCurrentId();ShoppingCart cart = shoppingCartMapper.selectOne(dto, currentUser);if (cart == null) {//不在购物车中,要新增到购物车里。准备一个entity对象cart = new ShoppingCart();BeanUtils.copyProperties(dto, cart);cart.setUserId(currentUser);cart.setCreateTime(LocalDateTime.now());cart.setNumber(1);//还需要判断添加的是套餐还是菜品,补全不同的数据if (dto.getDishId() != null) {//添加的是菜品,查询菜品信息Dish dish = dishMapper.selectById(dto.getDishId());cart.setName(dish.getName());cart.setImage(dish.getImage());cart.setAmount(dish.getPrice());}else{//添加的是套餐,查询套餐信息Setmeal setmeal = setmealMapper.selectById(dto.getSetmealId());cart.setName(setmeal.getName());cart.setImage(setmeal.getImage());cart.setAmount(setmeal.getPrice());}//保存到数据库里shoppingCartMapper.insert(cart);}else{//在购物车中,要修改购物车中商品的数量+1shoppingCartMapper.updateNumber(cart.getId(), 1);}return Result.success();}
}
5 ShoppingCartMapper
创建SShoppingCartMapper接口:
package com.sky.mapper;import com.sky.dto.ShoppingCartDTO;
import com.sky.entity.ShoppingCart;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Update;@Mapper
public interface ShoppingCartMapper {ShoppingCart selectOne(ShoppingCartDTO dto, Long userId);@Update("update shopping_cart set number = number + #{increment} where id = #{id}")void updateNumber(Long id, int increment);@Insert("insert into shopping_cart (name, image, user_id, dish_id, setmeal_id, dish_flavor, number, amount, create_time)" +"values (#{name}, #{image}, #{userId}, #{dishId}, #{setmealId}, #{dishFlavor}, #{number}, #{amount}, #{createTime})")void insert(ShoppingCart cart);
}
6 ShoppingCartMapper.xml
创建ShoppingCartMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sky.mapper.ShoppingCartMapper"><select id="selectOne" resultType="com.sky.entity.ShoppingCart">select * from shopping_cart<where><if test="userId!=null">and user_id = #{userId}</if><if test="dto.dishId!=null">and dish_id = #{dto.dishId}</if><if test="dto.setmealId!=null">and setmeal_id = #{dto.setmealId}</if><if test="dto.dishFlavor!=null and dto.dishFlavor.length()>0">and dish_flavor = #{dto.dishFlavor}</if></where></select>
</mapper>
3. 功能测试
进入小程序,添加菜品
五、查看购物车
1. 需求分析和设计
1 产品原型
当用户添加完菜品和套餐后,可进入到购物车中,查看购物中的菜品和套餐。
2 接口设计
2. 代码开发
1 ShoppingCartController
在ShoppingCartController中创建查看购物车的方法:
@GetMapping("/list")
@ApiOperation("查看购物车")
public Result queryCart(){return cartService.queryCart();
}
2 ShoppingCartService
在ShoppingCartService接口中声明查看购物车的方法:
/*** 查询当前用户的购物车* @return*/Result queryCart();
3 ShoppingCartServiceImpl
在ShoppingCartServiceImpl中实现查看购物车的方法:
@Overridepublic Result queryCart() {Long currentUser = BaseContext.getCurrentId();List<ShoppingCart> cartList = shoppingCartMapper.selectListByUser(currentUser);return Result.success(cartList);}
4 ShoppingCartMapper
@Select("select * from shopping_cart where user_id = #{userId}")
List<ShoppingCart> selectListByUser(Long userId);
3. 功能测试
当进入小程序时,就会发起查看购物车的请求
六、清空购物车
1. 需求分析和设计
1 产品原型
当点击清空按钮时,会把购物车中的数据全部清空。
2 接口设计
2. 代码开发
1 ShoppingCartController
在ShoppingCartController中创建清空购物车的方法:
@DeleteMapping("/clean")
@ApiOperation("清空购物车")
public Result cleanCart(){return cartService.cleanCart();
}
2 ShoppingCartService
在ShoppingCartService接口中声明清空购物车的方法:
/*** 清空购物车* @return*/Result cleanCart();
3 ShoppingCartServiceImpl
在ShoppingCartServiceImpl中实现清空购物车的方法:
@Override
public Result cleanCart() {shoppingCartMapper.deleteByUser(BaseContext.getCurrentId());return Result.success();
}
4 ShoppingCartMapper
在ShoppingCartMapper接口中创建删除购物车数据的方法:
@Delete("delete from shopping_cart where user_id = #{userId}")
void deleteByUser(Long userId);
3. 功能测试
进入到购物车页面
点击清空
七、删除购物车中一个商品
//controller@PostMapping("/sub")@ApiOperation("删除购物车中一个商品")public Result deleteOne(@RequestBody ShoppingCartDTO dto){return cartService.deleteOne(dto);
}-----------------
//ShoppingCartServiceResult deleteOne(ShoppingCartDTO dto);-----------------
//ShoppingCartServiceImpl@Overridepublic Result deleteOne(ShoppingCartDTO dto) {Long currentUser = BaseContext.getCurrentId();ShoppingCart cart = shoppingCartMapper.selectOne(dto, currentUser);if (cart.getNumber()==1){shoppingCartMapper.deleteById(cart.getId());}shoppingCartMapper.updateNumber(cart.getId(), -1);return Result.success();
}-------------------
//mapper
ShoppingCart selectOne(ShoppingCartDTO dto, Long userId);@Update("update shopping_cart set number=number+#{increment} where id=#{id}")void updateNumber(Long id, int increment);------------
//XML
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.ShoppingCartMapper"><select id="selectOne" resultType="com.sky.entity.ShoppingCart">select * from shopping_cart<where><if test="userId!=null">and user_id = #{userId}</if><if test="dto.dishId!=null">and dish_id = #{dto.dishId}</if><if test="dto.setmealId!=null">and setmeal_id = #{dto.setmealId}</if><if test="dto.dishFlavor!=null and dto.dishFlavor.length()>0">and dish_flavor = #{dto.dishFlavor}</if></where></select>
</mapper>
相关文章:

苍穹外卖07(缓存菜品,SpringCache,缓存套餐,添加购物车菜品和套餐多下单,查看购物车,清除购物车,删除购物车中一个商品)
目录 一、缓存菜品 1 问题说明 2 实现思路 3 代码开发:修改DishServiceImpl 4 功能测试 二、SpringCache 1. 介绍 2. 使用语法 1 起步依赖 2 使用要求 3 常用注解 4 SpEL表达式(了解备用) 5 步骤小结 3.入门案例 1 准备环境 2 使用入门 1 引导类上加…...

C语言第三十八弹---编译和链接
✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】 编译和链接 1、翻译环境和运行环境 2、翻译环境 2.1、预处理(预编译) 2.2、编译 2.2.1、词法分析 2.2.2、语法分析 2.2.3、语义分…...

无人售货奶柜:开启便捷生活的新篇章
无人售货奶柜:开启便捷生活的新篇章 在这个快节奏的现代生活中,科技的革新不仅为我们带来了前所未有的便利,更在不经意间改变着我们的日常。其中,无人售货技术的出现,尤其是无人售货奶柜,已经成为我们生活…...
STM32为什么不能跑Linux?
STM32是一系列基于ARM Cortex-M微控制器的产品,它们主要用于嵌入式系统中。而Linux则是一个开源的类Unix操作系统,主要面向的是桌面电脑、服务器等资源丰富的计算机。虽然理论上可以将Linux移植到STM32上运行,但是由于两者之间存在着很多技术…...
Dubbo 3.x源码(18)—Dubbo服务引用源码(1)
基于Dubbo 3.1,详细介绍了Dubbo服务的发布与引用的源码。 此前我们学习了Dubbo的服务导出的源码,在DubboBootstrapApplicationListener#startSync方法中,在调用了exportServices方法进行服务导出之后,立即调用了referServices方法…...
设计模式:工厂模式和抽象工厂模式的区别
定义 工厂模式(Factory Pattern)通常指的是工厂方法模式(Factory Method Pattern),它定义了一个创建对象的方法,由子类决定要实例化的类。工厂方法让类的实例化推迟到子类。 抽象工厂模式(Abstract Factory Pattern)提供了一个接口,用于创建相关或依赖对象的家族,而…...
python面试题(36~50)
36、如何取一个整数的绝对值? 这可以通过abs函数来实现。 abs(2) #> 2 abs(-2) #> 2 37、如何将两个列表组合成一个元组列表? 可以使用zip函数将列表组合成一个元组列表。这不仅仅限于使用两个列表。也适合3个或更多列表的情况。 a [a,b,c] b [1,2,3] [(k,v) fo…...

Vue 样式技巧总结与整理[中级局]
SFC(单文件组件)由 3 个不同的实体组成:模板、脚本和样式。三者都很重要,但后者往往被忽视,即使它可能变得复杂,且经常导致挫折和 bug。 更好的理解可以改善代码审查并减少调试时间。 这里有 7 个奇技淫巧…...

cesium加载.tif格式文件
最近项目中有需要直接加载三方给的后缀名tif格式的文件 <script src"https://cdn.jsdelivr.net/npm/geotiff"></script> 或者 yarn add geotiff npm install geotiff 新建tifs.js import GeoTIFF, { fromBlob, fromUrl, fromArrayBuffer } from geotif…...

分布式全闪占比剧增 152%,2023 年企业存储市场报告发布
近日,IDC 发布了 2023 年度的中国存储市场报告。根据该报告,在 2023 年软件定义存储的市场占比进一步扩大,分布式全闪的增长尤其亮眼,其市场份额从 2022 年的 7% 剧增到 2023 年的 17.7%,增长了 152%。 01 中国企业存…...
LeetCode 707. 设计链表(单链表、(非循环)双链表 模板)
你可以选择使用单链表或者双链表,设计并实现自己的链表。 单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。 如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点…...
深入了解Flutter中Overlay的介绍以及使用
Flutter Overlay 介绍 在 Flutter 中,Overlay 是一种特殊的 Widget,它可以用来在应用程序的其他部分之上显示内容。Overlay 非常适合用于显示模态对话框、弹出菜单、工具提示等。 Overlay 的工作原理 Overlay 位于 Flutter 的渲染树之外,这…...

文本直接生成2分钟视频,即将开源模型StreamingT2V
Picsart人工智能研究所、德克萨斯大学和SHI实验室的研究人员联合推出了StreamingT2V视频模型。通过文本就能直接生成2分钟、1分钟等不同时间,动作一致、连贯、没有卡顿的高质量视频。 虽然StreamingT2V在视频质量、多元化等还无法与Sora媲美,但在高速运…...

时序预测 | Matlab实现SOM-BP自组织映射结合BP神经网络时间序列预测
时序预测 | Matlab实现SOM-BP自组织映射结合BP神经网络时间序列预测 目录 时序预测 | Matlab实现SOM-BP自组织映射结合BP神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现SOM-BP自组织映射结合BP神经网络时间序列预测(完整源码…...

FPGA高端图像处理开发板-->鲲叔4EV:12G-SDI、4K HDMI2.0、MIPI等接口谁敢与我争锋?
目录 前言鲲叔4EV----高端FPGA图像处理开发板核心板描述底板描述配套例程源码描述配套服务描述开发板测试视频演示开发板获取 前言 在CSDN写博客传播FPGA开发经验已经一年多了,帮助了不少人,也得罪了不少人,有的人用我的代码赢得了某些比赛、…...

linux练习-交互式传参
在shell脚本中,read 向用户显示一行文本并接受用户输入 #!/bin/bash read -p 依次输入你的姓名、年龄、家乡 name age home echo 我是$name,年龄$age,我来自$home...

【数据结构(一)】初识数据结构
❣博主主页: 33的博客❣ ▶文章专栏分类: Java从入门到精通◀ 🚚我的代码仓库: 33的代码仓库🚚 🫵🫵🫵关注我带你学更多数据结构知识 目录 1.前言2.集合架构3.时间和空间复杂度3.1算法效率3.2时间复杂度3.2.1大O的渐进…...

前端三剑客 —— CSS (第六节)
目录 内容回顾: 弹性布局属性介绍 案例演示 商品案例 布局分析 登录案例 网格布局 内容回顾: 变量:定义变量使用 --名称:值; 使用变量: 属性名:var(--名称)&a…...

MyBatis 解决上篇的参数绑定问题以及XML方式交互
前言 上文:MyBatis 初识简单操作-CSDN博客 上篇文章我们谈到的Spring中如何使用注解对Mysql进行交互 但是我们发现我们返回出来的数据明显有问题 我们发现后面三个字段的信息明显没有展示出来 下面我们来谈谈解决方案 解决方案 这里的原因本质上是因为mysql中和对象中的字段属性…...
Rust语言之属性宏(Attribute Macro)derive
文章目录 Rust语言之属性宏(Attribute Macro)derive Rust语言之属性宏(Attribute Macro)derive 属性宏是一种基于属性的宏,用于修改、扩展或注解 Rust 代码。它们通常用于为函数、结构体、枚举、模块等添加元数据或自…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...
树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频
使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...