目录
[1 问题说明](#1 问题说明)
[2 实现思路](#2 实现思路)
[3 代码开发:修改DishServiceImpl](#3 代码开发:修改DishServiceImpl)
[4 功能测试](#4 功能测试)
[1. 介绍](#1. 介绍)
[2. 使用语法](#2. 使用语法)
[1 起步依赖](#1 起步依赖)
[2 使用要求](#2 使用要求)
[3 常用注解](#3 常用注解)
[4 SpEL表达式(了解备用)](#4 SpEL表达式(了解备用))
[5 步骤小结](#5 步骤小结)
[1 准备环境](#1 准备环境)
[2 使用入门](#2 使用入门)
[1 引导类上加@EnableCaching](#1 引导类上加@EnableCaching)
[2 更新缓存加@CachePut](#2 更新缓存加@CachePut)
[@CachePut 说明](#@CachePut 说明)
[3 使用缓存加@Cacheable](#3 使用缓存加@Cacheable)
[4 清理缓存加@CacheEvict](#4 清理缓存加@CacheEvict)
[4. 使用小结](#4. 使用小结)
[1 问题说明](#1 问题说明)
[2 实现思路](#2 实现思路)
[3 代码开发](#3 代码开发)
[1 添加依赖坐标](#1 添加依赖坐标)
[2 修改引导类加@EnableCaching](#2 修改引导类加@EnableCaching)
[3 修改SetmealServiceImpl](#3 修改SetmealServiceImpl)
[4 功能测试](#4 功能测试)
[1. 需求分析和设计](#1. 需求分析和设计)
[1 产品原型](#1 产品原型)
[2 接口设计](#2 接口设计)
[3 表设计](#3 表设计)
[2. 代码开发](#2. 代码开发)
[1 DTO设计](#1 DTO设计)
[2 ShoppingCartController](#2 ShoppingCartController)
[3 ShoppingCartService](#3 ShoppingCartService)
[4 ShoppingCartServiceImpl](#4 ShoppingCartServiceImpl)
[5 ShoppingCartMapper](#5 ShoppingCartMapper)
[6 ShoppingCartMapper.xml](#6 ShoppingCartMapper.xml)
[3. 功能测试](#3. 功能测试)
[1. 需求分析和设计](#1. 需求分析和设计)
[1 产品原型](#1 产品原型)
[2 接口设计](#2 接口设计)
[2. 代码开发](#2. 代码开发)
[1 ShoppingCartController](#1 ShoppingCartController)
[2 ShoppingCartService](#2 ShoppingCartService)
[3 ShoppingCartServiceImpl](#3 ShoppingCartServiceImpl)
[4 ShoppingCartMapper](#4 ShoppingCartMapper)
[3. 功能测试](#3. 功能测试)
[1. 需求分析和设计](#1. 需求分析和设计)
[1 产品原型](#1 产品原型)
[2 接口设计](#2 接口设计)
[2. 代码开发](#2. 代码开发)
[1 ShoppingCartController](#1 ShoppingCartController)
[2 ShoppingCartService](#2 ShoppingCartService)
[3 ShoppingCartServiceImpl](#3 ShoppingCartServiceImpl)
[4 ShoppingCartMapper](#4 ShoppingCartMapper)
[3. 功能测试](#3. 功能测试)
一、缓存菜品
1 问题说明
用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大。
2 实现思路
通过Redis来缓存菜品数据,减少数据库查询操作。
缓存逻辑分析:
-
每个分类下的菜品保存一份缓存数据
-
数据库中菜品数据有变更时清理缓存数据
要做的事情:使用缓存优化菜品相关的性能
-
给用户端的功能增加缓存:查询菜品时优先使用缓存
-
管理端功能增删改时要清理缓存:新增、修改、删除、上下架操作都要清理缓存
3 代码开发:修改DishServiceImpl
修改queryUserDishesByCategoryId方法,增加缓存
java
@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 的相关方法,加入清理缓存逻辑。
需要改造的方法:
-
新增菜品
-
修改菜品
-
批量删除菜品
-
起售、停售菜品
新增菜品时清理缓存
修改方法,增加清理缓存的代码
java
@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的id
List<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();
}
删除菜品时清理缓存
修改方法,增加清理缓存的代码
java
@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();
}
修改菜品时清理缓存
修改方法,增加清理缓存的代码
java
@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:
java
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
java
/**
* 新增用户方法
* 注解@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 ;
java
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 {
@Autowired
private UserService userService;
@Test
public 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
java
/**
* 根据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的缓存清除掉
然后在测试类里增加测试方法,并执行:
java
@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表达式
使用示例
java
@Override
@CacheEvict(cacheNames = "user", key = "#id")
public void deleteUser(Long id) {
System.out.println(">>>>UserServiceImpl.deleteUser方法执行了");
userMapper.deleteById(id);
}
在测试类里增加方法并执行:
java
@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注解,开启缓存注解功能
java
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
java
@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已定义
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
类
java
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 {
@Autowired
private ShoppingCartService cartService;
@PostMapping("/add")
@ApiOperation("添加购物车")
public Result addCart(@RequestBody ShoppingCartDTO dto){
return cartService.addCart(dto);
}
}
3 ShoppingCartService
创建ShoppingCartService接口:
java
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实现类
java
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 {
@Autowired
private ShoppingCartMapper shoppingCartMapper;
@Autowired
private DishMapper dishMapper;
@Autowired
private SetmealMapper setmealMapper;
@Override
public 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{
//在购物车中,要修改购物车中商品的数量+1
shoppingCartMapper.updateNumber(cart.getId(), 1);
}
return Result.success();
}
}
5 ShoppingCartMapper
创建SShoppingCartMapper接口:
java
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:
java
<?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>
3. 功能测试
进入小程序,添加菜品
五、查看购物车
1. 需求分析和设计
1 产品原型
当用户添加完菜品和套餐后,可进入到购物车中,查看购物中的菜品和套餐。
2 接口设计
2. 代码开发
1 ShoppingCartController
在ShoppingCartController中创建查看购物车的方法:
java
@GetMapping("/list")
@ApiOperation("查看购物车")
public Result queryCart(){
return cartService.queryCart();
}
2 ShoppingCartService
在ShoppingCartService接口中声明查看购物车的方法:
java
/**
* 查询当前用户的购物车
* @return
*/
Result queryCart();
3 ShoppingCartServiceImpl
在ShoppingCartServiceImpl中实现查看购物车的方法:
java
@Override
public Result queryCart() {
Long currentUser = BaseContext.getCurrentId();
List<ShoppingCart> cartList = shoppingCartMapper.selectListByUser(currentUser);
return Result.success(cartList);
}
4 ShoppingCartMapper
java
@Select("select * from shopping_cart where user_id = #{userId}")
List<ShoppingCart> selectListByUser(Long userId);
3. 功能测试
当进入小程序时,就会发起查看购物车的请求
六、清空购物车
1. 需求分析和设计
1 产品原型
当点击清空按钮时,会把购物车中的数据全部清空。
2 接口设计
2. 代码开发
1 ShoppingCartController
在ShoppingCartController中创建清空购物车的方法:
java
@DeleteMapping("/clean")
@ApiOperation("清空购物车")
public Result cleanCart(){
return cartService.cleanCart();
}
2 ShoppingCartService
在ShoppingCartService接口中声明清空购物车的方法:
java
/**
* 清空购物车
* @return
*/
Result cleanCart();
3 ShoppingCartServiceImpl
在ShoppingCartServiceImpl中实现清空购物车的方法:
java
@Override
public Result cleanCart() {
shoppingCartMapper.deleteByUser(BaseContext.getCurrentId());
return Result.success();
}
4 ShoppingCartMapper
在ShoppingCartMapper接口中创建删除购物车数据的方法:
java
@Delete("delete from shopping_cart where user_id = #{userId}")
void deleteByUser(Long userId);
3. 功能测试
进入到购物车页面
点击清空
七、删除购物车中一个商品
java
//controller
@PostMapping("/sub")
@ApiOperation("删除购物车中一个商品")
public Result deleteOne(@RequestBody ShoppingCartDTO dto){
return cartService.deleteOne(dto);
}
-----------------
//ShoppingCartService
Result deleteOne(ShoppingCartDTO dto);
-----------------
//ShoppingCartServiceImpl
@Override
public 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>