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

Day7 苍穹外卖项目 缓存菜品、SpringCache框架、缓存套餐、添加购物车、查看购物车、清空购物车

目录

1.缓存菜品

1.1 问题说明

1.2 实现思路

1.3 代码开发

1.3.1 加入缓存

1.3.2 清除缓存

1.3.2.1 新增菜品优化

1.3.2.2 菜品批量删除优化

1.3.2.3 修改菜品优化

1.3.2.4 菜品起售停售优化

1.4 功能测试

1.4.1 加入缓存

1.4.2 菜品修改

1.5 代码提交

2.缓存套餐

2.1 Spring Cache

2.1.1 介绍

2.1.2 常用注解

2.1.3 入门案例

2.1.3.1 环境准备

2.1.3.2 @CachePut注解

2.1.3.3 @Cacheable注解

2.1.3.4 @CacheEvict注解

2.2 实现步骤

2.3 代码开发

2.3.1 导入maven坐标

2.3.2 启动类加@EnableCaching注解

2.3.3 加@Cacheable注解

2.3.4 加@CacheEvict注解

2.4 功能测试

2.5 代码提交

3.添加购物车

3.1 需求分析和设计

3.1.1 产品原型

3.1.2 接口设计

3.1.3 表设计

3.2 代码开发

3.2.1 DTO设计

3.2.2 Controller层

3.2.3 Service层接口

3.2.4 Service层实现类

3.2.5 Mapper层

3.3 功能测试

3.4 代码提交

4.查看购物车

4.1 需求分析和设计

4.1.1 产品原型

4.1.2 接口设计

4.2 代码开发

4.2.1 Controller层

4.2.2 Service层接口

4.2.3 Service层实现类

4.3 功能测试

4.4 代码提交

5.清空购物车

5.1 需求分析和设计

5.1.1 产品原型

5.1.2 接口设计

5.2 代码开发

5.2.1 Controller层

5.2.2 Service层接口

5.2.3 Service层实现类

5.2.4 Mapper层

5.3 功能测试


1.缓存菜品

1.1 问题说明

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

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

1.2 实现思路

通过Redis来缓存菜品数据,减少数据库查询操作。

缓存逻辑分析:

  • 每个分类下的菜品保存一份缓存数据

  • 数据库中菜品数据有变更时清理缓存数据

1.3 代码开发

1.3.1 加入缓存

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

	@Autowiredprivate RedisTemplate redisTemplate;/*** 根据分类id查询菜品** @param categoryId* @return*/@GetMapping("/list")@ApiOperation("根据分类id查询菜品")public Result<List<DishVO>> list(Long categoryId) {//构造redis中的key,规则:dish_分类idString 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);}

1.3.2 清除缓存

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

需要改造的方法:

  • 新增菜品(删除dish_categoryId的缓存数据)

  • 修改菜品(删除有关dish_前缀的缓存数据)

  • 批量删除菜品(删除有关dish_前缀的缓存数据)

  • 起售、停售菜品(删除有关dish_前缀的缓存数据)

注意事项:如果加了缓存,要多查数据库,那就得不偿失。直接删除全部缓存、。

抽取清理缓存的方法:

在管理端DishController中添加

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

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

1.3.2.1 新增菜品优化

	/*** 新增菜品** @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();}

1.3.2.2 菜品批量删除优化

	/*** 菜品批量删除** @param ids* @return*/@DeleteMapping@ApiOperation("菜品批量删除")public Result delete(@RequestParam List<Long> ids) {log.info("菜品批量删除:{}", ids);dishService.deleteBatch(ids);//将所有的菜品缓存数据清理掉,所有以dish_开头的keycleanCache("dish_*");return Result.success();}

1.3.2.3 修改菜品优化

	/*** 修改菜品** @param dishDTO* @return*/@PutMapping@ApiOperation("修改菜品")public Result update(@RequestBody DishDTO dishDTO) {log.info("修改菜品:{}", dishDTO);dishService.updateWithFlavor(dishDTO);//将所有的菜品缓存数据清理掉,所有以dish_开头的keycleanCache("dish_*");return Result.success();}

1.3.2.4 菜品起售停售优化

	/*** 菜品起售停售** @param status* @param id* @return*/@PostMapping("/status/{status}")@ApiOperation("菜品起售停售")public Result<String> startOrStop(@PathVariable Integer status, Long id) {dishService.startOrStop(status, id);//将所有的菜品缓存数据清理掉,所有以dish_开头的keycleanCache("dish_*");return Result.success();}

1.4 功能测试

可以通过如下方式进行测试:

  • 查看控制台sql

  • 前后端联调

  • 查看Redis中的缓存数据

加入缓存菜品修改两个功能测试为例,通过前后端联调方式,查看控制台sql的打印和Redis中的缓存数据变化。

1.4.1 加入缓存

当第一次查询某个分类的菜品时,会从数据为中进行查询,同时将查询的结果存储到Redis中,在后绪的访问,若查询相同分类的菜品时,直接从Redis缓存中查询,不再查询数据库。

登录小程序:选择蜀味牛蛙(id=17)

查看控制台sql:有查询语句,说明是从数据库中进行查询。

查看Redis中的缓存数据:说明缓存成功。

再次访问:选择蜀味牛蛙(id=17)

说明是从Redis中查询的数据。

1.4.2 菜品修改

当在后台修改菜品数据时,为了保证Redis缓存中的数据和数据库中的数据时刻保持一致,当修改后,需要清空对应的缓存数据。用户再次访问时,还是先从数据库中查询,同时再把查询的结果存储到Redis中,这样,就能保证缓存和数据库的数据保持一致。

进入后台:修改蜀味牛蛙分类下的任意一个菜品,当前分类的菜品数据已在Redis中缓存

修改:

查看Redis中的缓存数据:说明修改时,已清空缓存。

用户再次访问同一个菜品分类时,需要先查询数据库,再把结果同步到Redis中,保证了两者数据一致性。

其它功能测试步骤基本一致,自已测试即可。

1.5 代码提交

2.缓存套餐

2.1 Spring Cache

2.1.1 介绍

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

在spring boot项目中,使用缓存技术只需在项目中导入相关缓存技术的依赖包,并在启动类上使用@EnableCaching开启缓存支持即可。

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

  • EHCache

  • Caffeine

  • Redis(常用)

注意事项:切换缓存实现,只需要引入其maven坐标,就能实现。

起步依赖:

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

引入Redis中间件依赖:

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

2.1.2 常用注解

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

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

2.1.3 入门案例

2.1.3.1 环境准备

导入基础工程:底层已使用Redis缓存实现

基础环境的代码,在我们今天的资料中已经准备好了, 大家只需要将这个工程导入进来就可以了。导入进来的工程结构如下:

数据库准备:

创建名为spring_cache_demo数据库,将springcachedemo.sql脚本直接导入数据库中。

引导类上加@EnableCaching:

开启缓存注解功能:

​@Slf4j
@SpringBootApplication
@EnableCaching//开启缓存注解功能
public class CacheDemoApplication {public static void main(String[] args) {SpringApplication.run(CacheDemoApplication.class,args);log.info("项目启动成功...");}
}

2.1.3.2 @CachePut注解

@CachePut 说明:

作用: 将方法返回值,放入缓存。

value: 缓存的名称, 每个缓存名称下面可以有很多key。

key: 缓存的key ----------> 支持Spring的表达式语言SPEL语法。

在save方法上加注解@CachePut

当前UserController的save方法是用来保存用户信息的,我们希望在该用户信息保存到数据库的同时,也往缓存中缓存一份数据,我们可以在save方法上加上注解 @CachePut,用法如下:

    /*** CachePut:将方法返回值放入缓存* value:缓存的名称,每个缓存名称下面可以有多个key* key:缓存的key*/@PostMapping@CachePut(value = "userCache", key = "#user.id")//key的生成:userCache::1public User save(@RequestBody User user){userMapper.insert(user);return user;}

注意事项:最终key的值会受到value以及key这两个属性影响;

// key的生成:userCache::1

@CachePut(value = "userCache", key = "#user.id")

Spring Expression Language (SpEL) expression for computing the key dynamically:

说明: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 ;

启动服务,通过swagger接口文档测试,访问UserController的save()方法

因为id是自增,所以不需要设置id属性

查看user表中的数据:

查看Redis中的数据:

2.1.3.3 @Cacheable注解

@Cacheable 说明:

作用: 在方法执行前,spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中。

value: 缓存的名称,每个缓存名称下面可以有多个key。

key: 缓存的key ----------> 支持Spring的表达式语言SPEL语法。

在getById上加注解@Cacheable

 /*** Cacheable:在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,    *调用方法并将方法返回值放到缓存中* value:缓存的名称,每个缓存名称下面可以有多个key* key:缓存的key*/@GetMapping@Cacheable(cacheNames = "userCache",key="#id")public User getById(Long id){User user = userMapper.getById(id);return user;}

重启服务,通过swagger接口文档测试,访问UserController的getById()方法。

第一次访问,会请求我们controller的方法,查询数据库。后面再查询相同的id,就直接从Redis中查询数据,不用再查询数据库了,就说明缓存生效了。

提前在redis中手动删除掉id=1的用户数据。

查看控制台sql语句:说明从数据库查询的用户数据。

查看Redis中的缓存数据:说明已成功缓存。

再次查询相同id的数据时,直接从redis中直接获取,不再查询数据库。

2.1.3.4 @CacheEvict注解

@CacheEvict 说明:

作用: 清理指定缓存

value: 缓存的名称,每个缓存名称下面可以有多个key

key: 缓存的key ----------> 支持Spring的表达式语言SPEL语法

删除单个缓存数据:

  @CacheEvict(cacheNames = "userCache",key = "#id")//删除某个key对应的缓存数据

删除所有缓存数据:

 @CacheEvict(value = "userCache",allEntries = true)

在 delete 方法上加注解@CacheEvict

    @DeleteMapping@CacheEvict(cacheNames = "userCache",key = "#id")//删除某个key对应的缓存数据public void deleteById(Long id){userMapper.deleteById(id);}
​@DeleteMapping("/delAll")@CacheEvict(cacheNames = "userCache",allEntries = true)//删除userCache下所有的缓存数据public void deleteAll(){userMapper.deleteAll();}

重启服务,通过swagger接口文档测试,访问UserController的deleteAll()方法。

查看user表:数据清空

查询Redis缓存数据:

2.2 实现步骤

实现步骤:

1). 导入Spring Cache和Redis相关maven坐标

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

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

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

2.3 代码开发

管理端有数据变动了必须清理缓存(但是不需要缓存数据),不然用户读到的数据就是缓存里的脏数据(用户端接口缓存数据)。

按照上述实现步骤:

2.3.1 导入maven坐标

<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.3.2 启动类加@EnableCaching注解

@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
@EnableCaching
public class SkyApplication {public static void main(String[] args) {SpringApplication.run(SkyApplication.class, args);log.info("server started");}
}

2.3.3 加@Cacheable注解

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

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

2.3.4 加@CacheEvict注解

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

    /*** 新增套餐** @param setmealDTO* @return*/@PostMapping@ApiOperation("新增套餐")@CacheEvict(cacheNames = "setmealCache",key = "#setmealDTO.categoryId")//key: setmealCache::100public 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();}

2.4 功能测试

通过前后端联调方式来进行测试,同时观察redis中缓存的套餐数据。和缓存菜品功能测试基本一致,不再赘述。

2.5 代码提交

3.添加购物车

3.1 需求分析和设计

3.1.1 产品原型

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

效果图:

3.1.2 接口设计

通过上述原型图,设计出对应的添加购物车接口。

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

3.1.3 表设计

用户的购物车数据,也是需要保存在数据库中的,购物车对应的数据表为shopping_cart表,具体表结构如下:

注意事项:这里的冗余字段,可以增加查询效率。但是不可大量使用。一般用于比较固定的数据字段。

字段名数据类型说明备注
idbigint主键自增
namevarchar(32)商品名称冗余字段
imagevarchar(255)商品图片路径冗余字段
user_idbigint用户id逻辑外键
dish_idbigint菜品id逻辑外键
setmeal_idbigint套餐id逻辑外键
dish_flavorvarchar(50)菜品口味
numberint商品数量
amountdecimal(10,2)商品单价冗余字段
create_timedatetime创建时间

说明:

  • 购物车数据是关联用户的,在表结构中,我们需要记录,每一个用户的购物车数据是哪些

  • 菜品列表展示出来的既有套餐,又有菜品,如果用户选择的是套餐,就保存套餐ID(setmeal_id),如果用户选择的是菜品,就保存菜品ID(dish_id)

  • 对同一个菜品/套餐,如果选择多份不需要添加多条记录,增加数量number即可

3.2 代码开发

3.2.1 DTO设计

根据添加购物车接口的参数设计DTO:

在sky-pojo模块,ShoppingCartDTO.java已定义

@Data
public class ShoppingCartDTO implements Serializable {private Long dishId;private Long setmealId;private String dishFlavor;}

3.2.2 Controller层

根据添加购物车接口创建ShoppingCartController:

/*** 购物车*/
@RestController
@RequestMapping("/user/shoppingCart")
@Slf4j
@Api(tags = "C端-购物车接口")
public class ShoppingCartController {@Autowiredprivate ShoppingCartService shoppingCartService;/*** 添加购物车* @param shoppingCartDTO* @return*/@PostMapping("/add")@ApiOperation("添加购物车")public Result<String> add(@RequestBody ShoppingCartDTO shoppingCartDTO){log.info("添加购物车:{}", shoppingCartDTO);shoppingCartService.addShoppingCart(shoppingCartDTO);//后绪步骤实现return Result.success();}
}

3.2.3 Service层接口

创建ShoppingCartService接口:

public interface ShoppingCartService {/*** 添加购物车* @param shoppingCartDTO*/void addShoppingCart(ShoppingCartDTO shoppingCartDTO);
}

3.2.4 Service层实现类

创建ShoppingCartServiceImpl实现类,并实现add方法:

@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {@Autowiredprivate ShoppingCartMapper shoppingCartMapper;@Autowiredprivate DishMapper dishMapper;@Autowiredprivate 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) {//如果已经存在,就更新数量,数量加1shoppingCart = 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);}}
}

3.2.5 Mapper层

创建ShoppingCartMapper接口:

@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);}

创建ShoppingCartMapper.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="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>

3.3 功能测试

进入小程序,添加菜品

加入购物车,查询数据库

因为现在没有实现查看购物车功能,所以只能在表中进行查看。

在前后联调时,后台可通断点方式启动,查看运行的每一步。

3.4 代码提交

4.查看购物车

4.1 需求分析和设计

4.1.1 产品原型

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

4.1.2 接口设计

4.2 代码开发

4.2.1 Controller层

在ShoppingCartController中创建查看购物车的方法:

	/*** 查看购物车* @return*/@GetMapping("/list")@ApiOperation("查看购物车")public Result<List<ShoppingCart>> list(){return Result.success(shoppingCartService.showShoppingCart());}

4.2.2 Service层接口

在ShoppingCartService接口中声明查看购物车的方法:

	/*** 查看购物车* @return*/List<ShoppingCart> showShoppingCart();

4.2.3 Service层实现类

在ShoppingCartServiceImpl中实现查看购物车的方法:

	/*** 查看购物车* @return*/public List<ShoppingCart> showShoppingCart() {return shoppingCartMapper.list(ShoppingCart.builder().userId(BaseContext.getCurrentId()).build());}

4.3 功能测试

当进入小程序时,就会发起查看购物车的请求

4.4 代码提交

5.清空购物车

5.1 需求分析和设计

5.1.1 产品原型

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

5.1.2 接口设计

5.2 代码开发

5.2.1 Controller层

在ShoppingCartController中创建清空购物车的方法:

	/*** 清空购物车商品* @return*/@DeleteMapping("/clean")@ApiOperation("清空购物车商品")public Result<String> clean(){shoppingCartService.cleanShoppingCart();return Result.success();}

5.2.2 Service层接口

在ShoppingCartService接口中声明清空购物车的方法:

	/*** 清空购物车商品*/void cleanShoppingCart();

5.2.3 Service层实现类

在ShoppingCartServiceImpl中实现清空购物车的方法:

	/*** 清空购物车商品*/public void cleanShoppingCart() {shoppingCartMapper.deleteByUserId(BaseContext.getCurrentId());}

5.2.4 Mapper层

在ShoppingCartMapper接口中创建删除购物车数据的方法:

	/*** 根据用户id删除购物车数据** @param userId*/@Delete("delete from shopping_cart where user_id = #{userId}")void deleteByUserId(Long userId);

5.3 功能测试

进入到购物车页面

点击清空:

相关文章:

Day7 苍穹外卖项目 缓存菜品、SpringCache框架、缓存套餐、添加购物车、查看购物车、清空购物车

目录 1.缓存菜品 1.1 问题说明 1.2 实现思路 1.3 代码开发 1.3.1 加入缓存 1.3.2 清除缓存 1.3.2.1 新增菜品优化 1.3.2.2 菜品批量删除优化 1.3.2.3 修改菜品优化 1.3.2.4 菜品起售停售优化 1.4 功能测试 1.4.1 加入缓存 1.4.2 菜品修改 1.5 代码提交 2.缓存套餐 2.1 Spring C…...

天天 AI-241207:今日热点- Windsurf:在工程能力上进一步进化的Cursor

2AGI.NET | 探索 AI 无限潜力&#xff0c;2AGI 为您带来最前沿资讯。 Windsurf&#xff1a;在工程能力上进一步进化的Cursor 介绍了一个新的AI代码编辑器Windsurf&#xff0c;它被认为是Cursor的进化版&#xff0c;具有更强的工程能力。文章强调了Windsurf在自动化编码和系统…...

Windows远程桌面连接到Linux

我的电脑是一台瘦客户端&#xff0c;公司设置的不能安装其他软件&#xff0c;里面只有几个软件&#xff0c;还好有一个远程桌面&#xff08;Remote Desktop Connection&#xff09;&#xff0c;我想连接到另一台Linux的电脑上。 在Linux上安装xrdp&#xff1a; sudo apt insta…...

使用前,后端写 具有分页效果的数据展示

目录 前言 效果展示图如下 思路 服务器从前端界面获得什么&#xff1f; 前端界面从后端服务器应该拿到什么&#xff1f; 使用的技术 代码 前端代码 list.jsp中该功能的实现代码 后端代码 对应的servlet 代码 实体类代码 service的实现层 实现该功能的代码 dao层 实…...

ubuntu防火墙管理(六)——ebtables

ebtables 是一个用于管理以太网帧的防火墙工具&#xff0c;主要用于在数据链路层&#xff08;第 2 层&#xff09;过滤和控制网络流量。它类似于 iptables&#xff0c;但专注于以太网流量。以下是 ebtables 的基本使用方法和示例。 基本命令 ebtables 的基本命令格式如下&…...

Oracle开发和应用——常用对象(表)

6.5. 常用对象 6.5.1. 表 1)概念 表(Table),是关系库中最基本、也是最常用的数据库对象,用户的数据存储在表中,用户使用数据时可以随时通过表进行检索或操作。也可以说,表是关系库最基本、最根本的特征。我们可以通过查询系统视图来获取表的相关信息。 D:\> sqlp…...

嵌入式蓝桥杯学习8 模拟电压测量

这里本来是要讲输入捕获的知识点的&#xff0c;但是由于学校校赛时间比较紧&#xff0c;校赛没考到输入捕获&#xff0c;所以先写ADC模拟电压测量的知识点。这里将的是单通道阻塞式采样。 Cubemx配置 点开cubemx。 1.将PB15配置为ADC2-IN15。 2.在Analog中点击ADC2&#xff…...

FFmpeg源码中,计算CRC校验的实现

一、CRC简介 CRC(Cyclic Redundancy Check)&#xff0c;即循环冗余校验&#xff0c;是一种根据网络数据包或电脑文件等数据产生简短固定位数校核码的快速算法&#xff0c;主要用来检测或校核数据传输或者保存后可能出现的错误。CRC利用除法及余数的原理&#xff0c;实现错误侦…...

Android笔记【14】结合LaunchedEffect实现计时器功能。

一、问题 cy老师第五次作业 结合LaunchedEffect实现计时器功能。要求&#xff1a;动态计时&#xff0c;每秒修改时间&#xff0c;计时的时间格式为“00&#xff1a;00&#xff1a;00”&#xff08;小时&#xff1a;分钟&#xff1a;秒&#xff09;提交源代码的文本和运行截图…...

kubectl 和 kubeconfig 基本原理

云原生学习路线导航页&#xff08;持续更新中&#xff09; kubernetes学习系列 快捷连接 Kubernetes架构原则和对象设计&#xff08;一&#xff09; 本文介绍kubectl的几个常用命令&#xff0c;kubconfig文件基本属性&#xff0c;并开启kubectl debug日志分析其背后基本原理 …...

LVGL笔录----动画

最近在搞LVGL动画内容&#xff0c;发现网上能参考的资源太少了。现将自己学习到的内容做个笔录&#xff0c;仅供自己记录&#xff0c;若对你有帮助&#xff0c;那么最好不过&#xff0c;共勉&#xff01; 首先&#xff0c;我是在CodeBlock上仿真 #define PI 3.14159265359stat…...

【LeetCode热题100】BFS解决FloodFill算法

这篇博客主要记录了使用BFS解决FloodFill算法的几道题目&#xff0c;包括图像渲染、岛屿数量、岛屿的最大面积、被包围的区域。 class Solution {using PII pair<int, int>; public:vector<vector<int>> floodFill(vector<vector<int>>& im…...

设计模式の软件设计原则

文章目录 前言一、聚合&组合&继承&依赖1.1、继承1.2、组合1.3、聚合1.4、依赖 二、单一职责原则2.1、单一职责原则反面案例2.2、单一职责原则反面案例的改进 三、接口隔离原则3.1、接口隔离原则反面案例3.2、接口隔离原则反面案例的改进 四、依赖倒转原则4.1、依赖…...

Linux centos7 下载MySQL5.7仓库的命令

wget 是一个非常强大的命令行工具&#xff0c;用于从网络上下载文件。它是 Linux 和其他 Unix-like 系统中常用的工具之一。wget 命令的各个参数有着不同的含义&#xff0c;下面是您提供的命令 wget -i -c http://dev.mysql.com/get/mysql57-community-release-el7-10.onarch.r…...

CSS flex布局踩坑小记:flex-basis属性之0px与0%的差异 (赞)

原文出处&#xff1a;CSS flex布局踩坑小记&#xff1a;flex-basis属性之0px与0%的差异_flex-basis 0%-CSDN博客 讲述flex容器被撑大的原因(误用&#xff1a;flex-basis: 0%;)及解决方法(用&#xff1a;flex-basis: 0px;)...

华硕主板不能开启

正常流程&#xff1a; [主機板]BIOS如何設置主機板整合圖形(內顯)和獨立顯示卡同時顯示輸出 | 官方支援 | ASUS 台灣 如果开启了CSR兼容性模式&#xff0c;在BIOS里面&#xff0c;就必须关掉&#xff0c;才能支持多显示器&#xff0c;如下图显示的标识才会出现。...

室联人形机器人:家政服务任务结构化、技术要点、深入应用FPGA的控制系统框架设计(整合版)

目录&#xff1a; 0 引言 1 人形机器人对室内家政服务任务的结构化 1.1人形机器人在室内家政服务中的比较优势 1.1.1 人形机器人拟人性的7个维度 1.1.2 拟人性在室内家政服务工作中的比较优势 1.1.3 潜在的重要用户&#xff1a;宠物爱好者 1.2 居所室内环境的特征与结构…...

OpenAI 发布 o1 LLM,推出 ChatGPT Pro

OpenAI正式发布了专为复杂推理而构建的 OpenAI o1大型语言模型(LLM)。 该公司还推出了 ChatGPT Pro&#xff0c;这是一项每月 200 美元的套餐&#xff0c;包括无限制访问 OpenAI o1、o1-mini、GPT-4o 和高级语音对话。 OpenAI o1 从 9 月 12 日起在 ChatGPT 中推出预览版&…...

【MySQL】存储过程和触发器

MySQL存储过程和触发器 一、存储过程的介绍二、存储过程的相关操作2.1创建存储过程2.2查看存储过程2.4调用存储过程2.5删除存储过程 三、变量3.1系统变量3.2用户定义变量3.3局部变量 四、存储过程中的关键字4.1 if4.2参数4.3case4.4 while4.5repeat4.6 loop4.7游标4.8条件处理程…...

QT4和 QT5 槽函数连接的区别

正常连接方式 //QT4官方用列QLabel *label new QLabel;QScrollBar *scrollBar new QScrollBar;QObject::connect(scrollBar, SIGNAL(valueChanged(int)),label, SLOT(setNum(int)));//QT5官方用列QLabel *label new QLabel;QLineEdit *lineEdit new QLineEdit;QObject::c…...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

可靠性+灵活性:电力载波技术在楼宇自控中的核心价值

可靠性灵活性&#xff1a;电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中&#xff0c;电力载波技术&#xff08;PLC&#xff09;凭借其独特的优势&#xff0c;正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据&#xff0c;无需额外布…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

多种风格导航菜单 HTML 实现(附源码)

下面我将为您展示 6 种不同风格的导航菜单实现&#xff0c;每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

云原生玩法三问:构建自定义开发环境

云原生玩法三问&#xff1a;构建自定义开发环境 引言 临时运维一个古董项目&#xff0c;无文档&#xff0c;无环境&#xff0c;无交接人&#xff0c;俗称三无。 运行设备的环境老&#xff0c;本地环境版本高&#xff0c;ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...

基于Java+MySQL实现(GUI)客户管理系统

客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息&#xff0c;对客户进行统一管理&#xff0c;可以把所有客户信息录入系统&#xff0c;进行维护和统计功能。可通过文件的方式保存相关录入数据&#xff0c;对…...

SQL慢可能是触发了ring buffer

简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...

Linux nano命令的基本使用

参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时&#xff0c;显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...

代码规范和架构【立芯理论一】(2025.06.08)

1、代码规范的目标 代码简洁精炼、美观&#xff0c;可持续性好高效率高复用&#xff0c;可移植性好高内聚&#xff0c;低耦合没有冗余规范性&#xff0c;代码有规可循&#xff0c;可以看出自己当时的思考过程特殊排版&#xff0c;特殊语法&#xff0c;特殊指令&#xff0c;必须…...

十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建

【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建 前言准备工作第一部分:回顾 Django 内置的 `User` 模型第二部分:设计并创建 `Role` 和 `UserProfile` 模型第三部分:创建 Serializers第四部分:创建 ViewSets第五部分:注册 API 路由第六部分:后端初步测…...