SpringBoot+SSM项目实战 苍穹外卖(3)

继续上一节的内容,本节完成菜品管理功能,包括公共字段自动填充、新增菜品、菜品分页查询、删除菜品、修改菜品。

目录

  • 公共字段自动填充
  • 新增菜品
    • 文件上传实现
    • 新增菜品实现 useGeneratedKeys
  • 菜品分页查询
  • 删除菜品
  • 修改菜品
    • 根据id查询菜品实现
    • 修改菜品实现

公共字段自动填充

在上一章节我们已经完成了后台系统的员工管理功能和菜品分类功能的开发,在新增员工或者新增菜品分类时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工或者编辑菜品分类时需要设置修改时间、修改人等字段。如:

新增员工方法:

public void save(EmployeeDTO employeeDTO) {.......................//设置当前记录的创建时间和修改时间employee.setCreateTime(LocalDateTime.now());employee.setUpdateTime(LocalDateTime.now());//设置当前记录创建人id和修改人idemployee.setCreateUser(BaseContext.getCurrentId());employee.setUpdateUser(BaseContext.getCurrentId());.......................employeeMapper.insert(employee);
}

修改菜品分类方法:

public void update(CategoryDTO categoryDTO) {//....................................//设置修改时间、修改人category.setUpdateTime(LocalDateTime.now());category.setUpdateUser(BaseContext.getCurrentId());categoryMapper.update(category);
}

在每一个业务方法中进行操作, 编码相对冗余、繁琐,可以使用AOP切面编程,对于这些公共字段在某个地方统一处理,来简化开发,实现功能增强,来完成公共字段自动填充功能。一共有四个公共字段:

序号字段名含义数据类型操作类型
1create_time创建时间datetimeinsert
2create_user创建人idbigintinsert
3update_time修改时间datetimeinsert、update
4update_user修改人idbigintinsert、update

实现步骤:
1). 自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法

2). 自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值

3). 在 Mapper 的相应方法上加入 AutoFill 注解

自定义注解 AutoFill

sky-server模块,创建com.sky.annotation包:

/*** 自定义注解,用于标识某个方法需要进行功能字段自动填充处理*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {//数据库操作类型:UPDATE INSERTOperationType value();
}

其中OperationType已在sky-common模块中定义

package com.sky.enumeration;/*** 数据库操作类型*/
public enum OperationType {/*** 更新操作*/UPDATE,/*** 插入操作*/INSERT
}

自定义切面类 AutoFillAspect

在sky-server模块,创建com.sky.aspect包

/*** 自定义切面,实现公共字段自动填充处理逻辑*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {/*** 切入点* 还可以这样写切入点表达式 效率更高 先确定包和类范围 再根据注解匹配方法*/@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")public void autoFillPointCut(){}/*** 这样需要使用前置通知,在插入或者更新方法前把公共字段赋值* 在通知中进行公共字段的赋值*/@Before("autoFillPointCut()")public void autoFill(JoinPoint joinPoint){log.info("开始进行公共字段自动填充...");//获取到当前被拦截的方法上的数据库操作类型MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法签名对象AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象OperationType operationType = autoFill.value();//获得数据库操作类型//获取到当前被拦截的方法的参数--实体对象Object[] args = joinPoint.getArgs();if(args == null || args.length == 0){return;}Object entity = args[0];//我们写之前的代码时就已规定好将实体对象放在参数第一位//准备赋值的数据LocalDateTime now = LocalDateTime.now();Long currentId = BaseContext.getCurrentId();//根据当前不同的操作类型,为对应的属性通过反射来赋值if(operationType == OperationType.INSERT){//为4个公共字段赋值try {Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);//通过反射为对象属性赋值setCreateTime.invoke(entity,now);setCreateUser.invoke(entity,currentId);setUpdateTime.invoke(entity,now);setUpdateUser.invoke(entity,currentId);} catch (Exception e) {e.printStackTrace();}}else if(operationType == OperationType.UPDATE){ //更新操作不需要设置创造时间和创造用户id//为2个公共字段赋值try {Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);//通过反射为对象属性赋值setUpdateTime.invoke(entity,now);setUpdateUser.invoke(entity,currentId);} catch (Exception e) {e.printStackTrace();}}}
}

为了防止类目写错,也为了解耦控制,将方法类目定义为常量:

/*** 公共字段自动填充相关常量*/
public class AutoFillConstant {/*** 实体类中的方法名称*/public static final String SET_CREATE_TIME = "setCreateTime";public static final String SET_UPDATE_TIME = "setUpdateTime";public static final String SET_CREATE_USER = "setCreateUser";public static final String SET_UPDATE_USER = "setUpdateUser";
}

上面切面类中的代码需要结合注释细读,前置知识比较读多,有面向切面编程、java高级:反射、java高级:注解

完成上述代码后,将员工管理、菜品分类管理的新增和编辑方法中的公共字段赋值的代码注释。并在相应方法的Mapper接口上加入 AutoFill 注解。如CategoryMapper:

@Mapper
public interface CategoryMapper {/*** 插入数据* @param category*/@Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user)" +" VALUES" +" (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")@AutoFill(value = OperationType.INSERT)void insert(Category category);/*** 根据id修改分类* @param category*/@AutoFill(value = OperationType.UPDATE)void update(Category category);
}

功能测试

以新增菜品分类为例,进行测试,启动项目和Nginx,新增菜品分类,通过观察控制台输出的SQL来确定公共字段填充是否完成。category表中数据是否完成自动填充。

在这里插入图片描述

在这里插入图片描述

测试通过,提交代码到git。





新增菜品

添加菜品时需要选择当前菜品所属的菜品分类,并且需要上传菜品图片。

在这里插入图片描述

业务规则:
菜品名称必须是唯一的
菜品必须属于某个分类下,不能单独存在
新增菜品时可以根据情况选择菜品的口味,口味选项是前端写死的后端无法修改选项
每个菜品必须对应一张图片

接口设计:根据类型查询分类(已完成)
文件上传
新增菜品

请添加图片描述

请添加图片描述

请添加图片描述

新增菜品,其实就是将新增页面录入的菜品信息插入到dish表,如果添加了口味做法,还需要向dish_flavor表插入数据。所以在新增菜品时,涉及到两个表。

1. 菜品表:dish

字段名数据类型说明备注
idbigint主键自增
namevarchar(32)菜品名称唯一
category_idbigint分类id逻辑外键
pricedecimal(10,2)菜品价格
imagevarchar(255)图片路径
descriptionvarchar(255)菜品描述
statusint售卖状态1起售 0停售
create_timedatetime创建时间
update_timedatetime最后修改时间
create_userbigint创建人id
update_userbigint最后修改人id

2. 菜品口味表:dish_flavor

字段名数据类型说明备注
idbigint主键自增
dish_idbigint菜品id逻辑外键
namevarchar(32)口味名称
valuevarchar(255)口味值

逻辑外键指在建表时没有声明foreignkey,而是在代码上控制逻辑关联而产生的外键。




文件上传实现

本项目选用阿里云的OSS服务进行文件存储。(前面课程已学习过阿里云OSS,不再赘述)

定义OSS相关配置

sky-server模块 application-dev.yml:

这里只是示例,实际配置值需要自己申请阿里云oss并做相应修改。并且现在阿里云oss新版示例代码已经更新为将accessKeyId和accessKeySecret放在环境变量中。

sky:alioss:endpoint: oss-cn-beijing.aliyuncs.combucket-name: web-tx-36

application.yml:

spring:profiles:active: dev    #设置环境
sky:alioss:endpoint: ${sky.alioss.endpoint}bucket-name: ${sky.alioss.bucket-name}

注意具体的值没有直接配置在application.yml,其是当前项目的主配置文件,最终项目上线有可能该地方要更改,开发环境和生产环境有可能使用不同账号,所以是采用引用的方式,引用dev,表示开发环境,到时候可能还需要提供一个product即生产环境下的配置文件。到时候只需要把application.yml的active改成product就行了,这也是比较规范的使用方式。

读取OSS配置

sky-common模块中,已定义

@Component
@ConfigurationProperties(prefix = "sky.alioss")
@Data
public class AliOssProperties {private String endpoint;private String bucketName;
}

注意与application.yml中的书写不是完全一致,它那里使用横向来分割不同单词,而这里使用的是驼峰命名,springboot是会自动转换的,这种方式相对规范,当然如果yml中使用驼峰命名原封不动拷贝过来也是可以的,只不过不符合yml规范。

生成OSS工具类对象

sky-server模块

/*** 配置类,用于创建AliOssUtil对象*/
@Configuration
@Slf4j
public class OssConfiguration {@Bean // 项目启动时就会调用这个方法把对象创建出来交给spring容器管理@ConditionalOnMissingBean // 保证整个spring容器管理里只有一个AliOssUtil对象public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){log.info("开始创建阿里云文件上传工具类对象:{}",aliOssProperties);return new AliOssUtil(aliOssProperties.getEndpoint(),aliOssProperties.getBucketName());}
}

其中,AliOssUtil.java已在sky-common模块中定义

@Data
@AllArgsConstructor
@Slf4j
public class AliOssUtil {private String endpoint;private String bucketName;/*** 文件上传** @param bytes* @param objectName* @return*/public String upload(byte[] bytes, String objectName) {OSS ossClient = null;try {// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();// 创建OSSClient实例。ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);// 创建PutObject请求。ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));} catch (OSSException oe) {System.out.println("Caught an OSSException, which means your request made it to OSS, "+ "but was rejected with an error response for some reason.");System.out.println("Error Message:" + oe.getErrorMessage());System.out.println("Error Code:" + oe.getErrorCode());System.out.println("Request ID:" + oe.getRequestId());System.out.println("Host ID:" + oe.getHostId());} catch (ClientException ce) {System.out.println("Caught an ClientException, which means the client encountered "+ "a serious internal problem while trying to communicate with OSS, "+ "such as not being able to access the network.");System.out.println("Error Message:" + ce.getMessage());} catch (com.aliyuncs.exceptions.ClientException e) {System.out.println("环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET获取错误");System.out.println("Error Message:" + e.getMessage());} finally {if (ossClient != null) {ossClient.shutdown();}}//文件访问路径规则 https://BucketName.Endpoint/ObjectNameStringBuilder stringBuilder = new StringBuilder("https://");stringBuilder.append(bucketName).append(".").append(endpoint).append("/").append(objectName);log.info("文件上传到:{}", stringBuilder.toString());return stringBuilder.toString();}
}

定义文件上传接口

sky-server模块中定义接口

@RestController
@RequestMapping("/admin/common")
@Api(tags = "通用接口")
@Slf4j
public class CommonController {@Autowiredprivate AliOssUtil aliOssUtil;/*** 文件上传* @param file* @return*/@PostMapping("/upload")@ApiOperation("文件上传")public Result<String> upload(MultipartFile file){log.info("文件上传:{}",file);try {//原始文件名String originalFilename = file.getOriginalFilename();//截取原始文件名的后缀   dfdfdf.pngString extension = originalFilename.substring(originalFilename.lastIndexOf("."));//构造新文件名称String objectName = UUID.randomUUID().toString() + extension;//文件的请求路径String filePath = aliOssUtil.upload(file.getBytes(), objectName);return Result.success(filePath);} catch (IOException e) {log.error("文件上传失败:{}", e);}return Result.error(MessageConstant.UPLOAD_FAILED);}
}

直接前端测试,上传文件后网站能成功回显图片即成功。

请添加图片描述




新增菜品实现 useGeneratedKeys

设计DTO类

在sky-pojo模块中的DishDTO和DishFlavor

Controller层

sky-server模块

/*** 菜品管理*/
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {@Autowiredprivate DishService dishService;/*** 新增菜品** @param dishDTO* @return*/@PostMapping@ApiOperation("新增菜品")public Result save(@RequestBody DishDTO dishDTO) {log.info("新增菜品:{}", dishDTO);dishService.saveWithFlavor(dishDTO);return Result.success();}
}

Service层实现类

@Service
@Slf4j
public class DishServiceImpl implements DishService {@Autowiredprivate DishMapper dishMapper;@Autowiredprivate DishFlavorMapper dishFlavorMapper;/*** 新增菜品和对应的口味** @param dishDTO*/@Transactional // 涉及到两张表 使用事务注解public void saveWithFlavor(DishDTO dishDTO) {Dish dish = new Dish();BeanUtils.copyProperties(dishDTO, dish);//向菜品表插入1条数据dishMapper.insert(dish);//后绪步骤实现//获取insert语句生成的主键值Long dishId = dish.getId();List<DishFlavor> flavors = dishDTO.getFlavors();if (flavors != null && flavors.size() > 0) {flavors.forEach(dishFlavor -> {dishFlavor.setDishId(dishId);});//向口味表插入n条数据dishFlavorMapper.insertBatch(flavors);//后绪步骤实现}}
}

Mapper层

DishMapper

@AutoFill(value = OperationType.INSERT) // 公共字段填充
void insert(Dish dish);

这里插入口味表时要注意,口味表的数据插入需要对应菜品的id,但是在新增菜品时前端不可能返回菜品的id给我们,因为id也是我们通过insert语句自动生成的,因为我们在建表时设置了id自增,所以我们这里在菜品表的insert里,需要将insert之后生成的菜品id返回,这个动态sql也是可以实现的。通过useGeneratedKeys和keyProperty属性。useGeneratedKeys="true"表示我们需要获得insert之后生成的主键,keyProperty="id"表示希望把主键赋值给我们传入insert语句的参数Dish对象的id属性。

在/resources/mapper中创建DishMapper.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.DishMapper"><insert id="insert" useGeneratedKeys="true" keyProperty="id">insert into dish (name, category_id, price, image, description, create_time, update_time, create_user,update_user, status)values (#{name}, #{categoryId}, #{price}, #{image}, #{description}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser}, #{status})</insert>
</mapper>

DishFlavorMapper

void insertBatch(List<DishFlavor> flavors);

DishFlavorMapper.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.DishFlavorMapper"><insert id="insertBatch">insert into dish_flavor (dish_id, name, value) VALUES<foreach collection="flavors" item="df" separator=",">(#{df.dishId},#{df.name},#{df.value})</foreach></insert>
</mapper>

功能测试:

进入到菜品管理—>新建菜品,由于没有实现菜品查询功能,所以保存后,暂且在表中查看添加的数据。

请添加图片描述

请添加图片描述

请添加图片描述

测试成功,提交代码。





菜品分页查询

原型图:

在这里插入图片描述

在菜品列表展示时,除了菜品的基本信息(名称、售价、售卖状态、最后操作时间)外,还有两个字段略微特殊,第一个是图片字段 ,我们从数据库查询出来的仅仅是图片的名字,图片要想在表格中回显展示出来,就需要下载这个图片。第二个是菜品分类,这里展示的是分类名称,而不是分类ID,此时我们就需要根据菜品的分类ID,去分类表中查询分类信息,然后在页面展示。

业务规则:根据页码展示菜品信息;每页展示10条数据;分页查询时可以根据需要输入菜品名称、菜品分类、菜品状态进行查询

请添加图片描述

设计DTO与VO类

在sky-pojo模块中,已定义DishPageQueryDTO和DishVO

Controller层

根据接口定义创建DishController的page分页查询方法:

@GetMapping("/page")
@ApiOperation("菜品分页查询")
public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO) {log.info("菜品分页查询:{}", dishPageQueryDTO);PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);return Result.success(pageResult);
}

Service层实现类

在 DishServiceImpl 中实现分页查询方法:

public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);//后绪步骤实现return new PageResult(page.getTotal(), page.getResult());
}

Mapper层

在 DishMapper 接口中声明 pageQuery 方法:

Page<DishVO> pageQuery(DishPageQueryDTO dishPageQueryDTO);

在 DishMapper.xml 中编写SQL:

<select id="pageQuery" resultType="com.sky.vo.DishVO">select d.* , c.name as categoryName from dish d left outer join category c on d.category_id = c.id<where><if test="name != null">and d.name like concat('%',#{name},'%')</if><if test="categoryId != null">and d.category_id = #{categoryId}</if><if test="status != null">and d.status = #{status}</if></where>order by d.create_time desc
</select>

前端测试:

在这里插入图片描述

测试通过,提交代码。





删除菜品

在菜品列表页面,每个菜品后面对应的操作分别为修改、删除、停售,可通过删除功能完成对菜品及相关的数据进行删除。

在这里插入图片描述

业务规则:可以一次删除一个菜品,也可以批量删除菜品;起售中的菜品不能删除;被套餐关联的菜品不能删除;删除菜品后,关联的口味数据也需要删除掉

请添加图片描述

注意:删除一个菜品和批量删除菜品共用一个接口,故ids可包含多个菜品id,之间用逗号分隔。在进行删除菜品操作时,会涉及到以下三张表。

在这里插入图片描述

在dish表中删除菜品基本数据时,同时,也要把关联在dish_flavor表中的数据一块删除。
setmeal_dish表为菜品和套餐关联的中间表。若删除的菜品数据关联着某个套餐,此时,删除失败。
若要删除套餐关联的菜品数据,先解除两者关联,再对菜品进行删除。

Controller层

根据删除菜品的接口定义在DishController中创建方法:

@DeleteMapping
@ApiOperation("菜品批量删除")
public Result delete(@RequestParam List<Long> ids) {log.info("菜品批量删除:{}", ids);dishService.deleteBatch(ids);return Result.success();
}

Service层实现类

在DishServiceImpl中实现deleteBatch方法:

@Autowired
private SetmealDishMapper setmealDishMapper;@Transactional//事务
public void deleteBatch(List<Long> ids) {//判断当前菜品是否能够删除---是否存在起售中的菜品??for (Long id : ids) {Dish dish = dishMapper.getById(id);//后绪步骤实现if (dish.getStatus() == StatusConstant.ENABLE) {//当前菜品处于起售中,不能删除throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);}}//判断当前菜品是否能够删除---是否被套餐关联了??List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(ids);if (setmealIds != null && setmealIds.size() > 0) {//当前菜品被套餐关联了,不能删除throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);}//删除菜品表中的菜品数据for (Long id : ids) {dishMapper.deleteById(id);//后绪步骤实现//删除菜品关联的口味数据dishFlavorMapper.deleteByDishId(id);//后绪步骤实现}
}

Mapper层

在DishMapper中声明getById方法,并配置SQL:

@Select("select * from dish where id = #{id}")
Dish getById(Long id);

创建SetmealDishMapper,声明getSetmealIdsByDishIds方法,并在xml文件中编写SQL:

@Mapper
public interface SetmealDishMapper {/*** 根据菜品id查询对应的套餐id** @param dishIds* @return*///select setmeal_id from setmeal_dish where dish_id in (1,2,3,4)List<Long> getSetmealIdsByDishIds(List<Long> dishIds);
}

SetmealDishMapper.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.SetmealDishMapper"><select id="getSetmealIdsByDishIds" resultType="java.lang.Long">select setmeal_id from setmeal_dish where dish_id in<foreach collection="dishIds" item="dishId" separator="," open="(" close=")">#{dishId}</foreach></select>
</mapper>

在DishMapper.java中声明deleteById方法并配置SQL:

@Delete("delete from dish where id = #{id}")
void deleteById(Long id);

在DishFlavorMapper中声明deleteByDishId方法并配置SQL:

@Delete("delete from dish_flavor where dish_id = #{dishId}")
void deleteByDishId(Long dishId);

使用前后端联调测试,进入到菜品列表查询页面,对测试菜品进行删除操作,同时,进到dish表和dish_flavor两个表查看测试菜品的相关数据都已被成功删除。再次,删除状态为启售的菜品,提示起售中的菜品不能被删除。最后测试批量删除。

在这里插入图片描述

测试通过,提交代码。





修改菜品

在这里插入图片描述

通过对上述原型图进行分析,该页面共涉及4个接口:根据id查询菜品、根据类型查询分类(已实现)、文件上传(已实现)、修改菜品。下面分析这两个接口。

请添加图片描述

请添加图片描述



根据id查询菜品实现

Controller层

根据id查询菜品的接口定义在DishController中创建方法:

@GetMapping("/{id}")
@ApiOperation("根据id查询菜品")
public Result<DishVO> getById(@PathVariable Long id) {log.info("根据id查询菜品:{}", id);DishVO dishVO = dishService.getByIdWithFlavor(id);return Result.success(dishVO);
}

Service层实现类

public DishVO getByIdWithFlavor(Long id) {//根据id查询菜品数据Dish dish = dishMapper.getById(id);//根据菜品id查询口味数据List<DishFlavor> dishFlavors = dishFlavorMapper.getByDishId(id);//后绪步骤实现//将查询到的数据封装到VODishVO dishVO = new DishVO();BeanUtils.copyProperties(dish, dishVO);dishVO.setFlavors(dishFlavors);return dishVO;
}

Mapper层

@Select("select * from dish_flavor where dish_id = #{dishId}")
List<DishFlavor> getByDishId(Long dishId);

功能完成后点击修改菜品,能够在前端页面上回显菜品信息:

请添加图片描述



修改菜品实现

Controller层

根据修改菜品的接口定义在DishController中创建方法:

@PutMapping
@ApiOperation("修改菜品")
public Result update(@RequestBody DishDTO dishDTO) {log.info("修改菜品:{}", dishDTO);dishService.updateWithFlavor(dishDTO);return Result.success();
}

Service层实现类

在DishServiceImpl中实现updateWithFlavor方法:

@Transactional //事务
public void updateWithFlavor(DishDTO dishDTO) {Dish dish = new Dish();BeanUtils.copyProperties(dishDTO, dish);//修改菜品表基本信息dishMapper.update(dish);//删除原有的口味数据dishFlavorMapper.deleteByDishId(dishDTO.getId());//重新插入口味数据List<DishFlavor> flavors = dishDTO.getFlavors();if (flavors != null && flavors.size() > 0) {flavors.forEach(dishFlavor -> {dishFlavor.setDishId(dishDTO.getId());});//向口味表插入n条数据dishFlavorMapper.insertBatch(flavors);}
}

Mapper层

在DishMapper中,声明update方法:

@AutoFill(value = OperationType.UPDATE) // 公共字段填充
void update(Dish dish);

并在DishMapper.xml文件中编写SQL:

<update id="update">update dish<set><if test="name != null">name = #{name},</if><if test="categoryId != null">category_id = #{categoryId},</if><if test="price != null">price = #{price},</if><if test="image != null">image = #{image},</if><if test="description != null">description = #{description},</if><if test="status != null">status = #{status},</if><if test="updateTime != null">update_time = #{updateTime},</if><if test="updateUser != null">update_user = #{updateUser},</if></set>where id = #{id}
</update>

直接前后端联调测试修改菜品信息,修改各个信息进行测试。修改成功提交代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/196354.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

geemap学习笔记015:下载哨兵2号(Sentinel-2)数据

前言 使用GEE下载数据应该是最常见的功能了&#xff0c;今天就介绍一下如何使用geemap下载哨兵2号(Sentinel-2)数据&#xff0c;分别包括自己画感兴趣&#xff0c;以及利用Assets中的shp文件进行下载。 1 自己画感兴趣下载哨兵2号影像 import geemap import eeMap geemap.M…

金蝶云星空表单插件单据体批量删除,序号自增

文章目录 金蝶云星空表单插件单据体批量删除&#xff0c;序号自增字段标识说明表单插件获取单据体数据包移除物料为空的行其他移除物料为空的行的方式&#xff0c;但是测试不通过&#xff0c;不建议使用序号重新生成测试 金蝶云星空表单插件单据体批量删除&#xff0c;序号自增…

Apache Doris 详细教程(三)

7、监控和报警 Doris 可以使用 Prometheus 和 Grafana 进行监控和采集&#xff0c;官网下载最新版即可。 Prometheus 官网下载&#xff1a;https://prometheus.io/download/ Grafana 官网下载&#xff1a;https://grafana.com/grafana/download Doris 的监控数据通过 FE 和…

B2B公司如何寻找意向客户的联系方式?

在B2B公司的营销过程中&#xff0c;少不了寻找意向客户的阶段&#xff0c;这也是销售过程中非常重要的一步。 很多新人都是拿到客户联系方式&#xff0c;就直接打电话拜访&#xff0c;俗话说不打没有准备的仗&#xff0c;因此在拜访客户之前就应该做好功课&#xff0c;充分了解…

全网最新最全的自动化测试教程:python+pytest接口自动化-请求参数格式的确定

我们在做接口测试之前&#xff0c;先需要根据接口文档或抓包接口数据&#xff0c;搞清楚被测接口的详细内容&#xff0c;其中就包含请求参数的编码格式&#xff0c;从而使用对应的参数格式发送请求。例如某个接口规定的请求主体的编码方式为 application/json&#xff0c;那么在…

医院信息化专业人员必备医院业务运作及管理流程知识(详细)

业务流程是一家医院运作的基础,医院所有业务都需要流程加以驱动。熟知医院各项业务,了解医院管理流程,有利于医院工作人员更好地投入自身岗位,提高工作效率。本文整理了常见医院业务运作及管理流程,仅供参考! 【门诊业务】 一、门诊业务的特点: 1.接诊病人多,就诊时…

Python 接口自动化 —— requests框架

1.前言 Python内置的urllib模块&#xff0c;也可以用于访问网络资源。但是&#xff0c;它用起来比较麻烦&#xff0c;而且&#xff0c;缺少很多实用的高级功能。因此我们使用 requests 模块进行进行接口测试。 requests官方文档资料地址&#xff1a;http://cn.python-request…

记录 | ipad上安装ipa文件

目的&#xff1a;在 ipad 上安装 ipa 文件 首先需要在 mac 端安装 itools pro 下载地址&#xff1a;https://www.thinkskysoft.com/itools/ 然后下载 ipa > 需要有签名的&#xff0c;不然安装不了 然后用数据线连接 ipad 和 mac&#xff0c;应用 -> 安装

C语言——深入理解指针(5)

目录 1. sizeof和strlen的对比 1.1 sizeof 1.2 strlen 1.3 sizeof和strlen 的对比 2. 数据和指针题解析 2.1 一维数组 2.2 字符数组 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2,6 2.3 二维数组 3. 指针运算题解析 3.1 例1 3.2 例2 3.3 例3 3.4 例4 3.5 例5 3.6 例…

OpenCV介绍及安装

目录 1.OpenCV简介 2.OpenCV安装 3.检查OpenCV是否安装成功 4.OpenCV模块 5.学习技巧 1.OpenCV简介 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个基于开源发行的跨平台计算机视觉库&#xff0c;主要用于图像处理、计算机视觉和机器学习等领域…

干货|水表基础知识大全

第一部分 水表基础知识 第一节 水表的作用 水表:是用来记录流经自来水管道中水量的一种计量器具,也称为计量器具。 1、水表的发展简史 1825年英国的克路斯发明了真正具有仪表特征的平衡罐式水表以来&#xff0c;水表的发展已有近二百年的历史。期间&#xff0c;水表的结构…

目标检测中的损失函数:IOU_Loss、GIOU_Loss、DIOU_Loss和CIOU_Loss

文章目录 前言1.IOU_Loss&#xff08;Intersection over Union Loss&#xff09;2.GIOU_Loss&#xff08;Generalized Intersection over Union Loss&#xff09;3.DIOU_Loss&#xff08;Distance Intersection over Union Loss&#xff09;4.CIOU_Loss&#xff08;Complete In…

行云创新:与德国汽车业交流Catena-X数据空间技术有感

近日&#xff0c;行云创新CEO马洪喜先生受深圳高新投、Plug&Play邀请参加Catena-X技术研讨交流会&#xff0c;与西门子全球汽车生态系统总监Claus CREMERS&#xff0c;大众汽车全球数字化生产及流程负责人Frank GOELLER&#xff0c;宝马全球数字化政策负责人Sebastian SCHL…

【国际会议】2024年材料科学与机械应用发展国际会议(ICMSMAD 2024)

2024年材料科学与机械应用发展国际会议(ICMSMAD 2024) 2024 International Conference on Materials Science and Mechanical Application Development(ICMSMAD 2024) 一、【会议简介】 ​2024年材料科学与机械应用发展国际会议(ICMSMAD 2024)即将在美丽的中国成都盛大召开。这…

使用gdb调试正在运行的程序

写一个一秒打印一个数的c程序&#xff0c;并编译运行。 #include<unistd.h> #include<stdio.h> int main(int argc,char **argv){int i0;while(1){sleep(1);i;printf("%d\n",i);}return 0; }vim loop.c gcc loop.c -o loop ./loop 查看该进程的进程号。…

《数据库系统概论》学习笔记——王珊 萨师煊

第一章 绪论 一、数据库系统概述 1.数据库的4个基本概念 &#xff08;1&#xff09;数据 描述事物的符号记录称为数据 &#xff08;2&#xff09;数据库 存放数据的仓库 &#xff08;3&#xff09;数据库管理系统 主要功能&#xff1a; &#xff08;1&#xff09;数据定…

APP逆向工具环境安装

环境安装及配置&#xff1a; 1.JDK安装及配置链接&#xff1a;https://pan.baidu.com/s/146I4vDJdz8YeR0OEqLS8xw 提取码&#xff1a;7h00 2.SDK环境配置链接&#xff1a;https://pan.baidu.com/s/1A8rwqyw8Nn7p93Axqpll3A 提取码&#xff1a;cwv43.NDK环境配置链接&#xff1…

SpringBoot:SpringMVC(上)

文章目录 前言一、SpringMVC是什么&#xff1f;1.1 MVC的定义&#xff1a;1.2 MVC 和 Spring MVC 的关系 二、Spring MVC 创建和连接2.1创建springmvc2.2接下来&#xff0c;创建⼀个 UserController 类&#xff0c;实现⽤户到 Spring 程序的互联互通&#xff0c;具体实现代码如…

MYSQL练题笔记-聚合函数-即时食物配送

我做完上一道题&#xff0c;决定总结一下了&#xff0c;因为现在还是没有一个我认为好的思路去构造语句&#xff0c;这里开始试一试新的思路。果然想要好一点的时候&#xff0c;总是像便秘一下&#xff0c;真的想拉&#xff0c;但是真的难拉啊 一、题目相关内容 1&#xff09…

科普关于msvcp140.dll文件是什么内容,解决msvcp140.dll丢失的修复方法

在使用电脑时出现了关于msvcp140.dll丢失的问题&#xff0c;这是什么情况&#xff0c;出现这样的问题通常都会导致电脑中的一些软件不能使用&#xff0c;那么都有什么办法能够解决msvcp140.dll丢失的问题呢&#xff1f;今天就给大家科普一些关于msvcp140.dll文件是什么的内容&a…