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.rhkb.cn/news/210733.html

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

相关文章

Redis中的缓存穿透、雪崩、击穿(详细)

目录 一、概念 1. 缓存穿透&#xff08;Cache Penetration&#xff09; 解决方案&#xff1a; 2. 缓存雪崩&#xff08;Cache Avalanche&#xff09; 解决方案&#xff1a; 3. 缓存击穿&#xff08;Cache Breakdown&#xff09; 解决方案&#xff1a; 二、三者出现的根本原…

为XiunoBBS4.0开启redis缓存且支持密码验证

修改模块文件1 xiunoPHP/cache_redis.class.php: <?phpclass cache_redis {public $conf array();public $link NULL;public $cachepre ;public $errno 0;public $errstr ;public function __construct($conf array()) {if(!extension_loaded(Redis)) {return $thi…

有趣的代码——有故事背景的程序设计3

这篇文章再和大家分享一些有“背景”的程序设计&#xff0c;希望能够让大家学到知识的同时&#xff0c;对编程学习更感兴趣&#xff0c;更能在这条路上坚定地走下去。 目录 1.幻方问题 2.用函数打印九九乘法表 3.鸡兔同笼问题 4.字数统计 5.简单选择排序 1.幻方问题 幻方又…

Mac苹果视频剪辑:Final Cut Pro Mac

Final Cut Pro是一款由Apple公司开发的专业视频非线性编辑软件&#xff0c;是业界著名的视频剪辑软件之一。它最初发布于1999年&#xff0c;是Mac电脑上的一款独占软件。Final Cut Pro具有先进的剪辑工具、丰富的特效和颜色分级、音频处理等功能&#xff0c;使得用户可以轻松地…

Linux之重谈文件和c语言文件接口

重谈文件 文件 内容 属性, 所有对文件的操作都是: a.对内容操作 b.对属性操作 关于文件 一&#xff1a; 即使文件的内容为空&#xff0c;该文件也会在磁盘上也会占空间&#xff0c;因为文件不仅仅只有内容还有文件对应的属性&#xff0c;文件的内容会占用空间, 文件的属性也…

【面试】Java最新面试题资深开发-JVM第一弹

问题一&#xff1a;Java中的垃圾回收机制 在Java中&#xff0c;垃圾回收是如何工作的&#xff0c;可以简要描述一下垃圾回收的算法有哪些吗&#xff1f; 在Java中&#xff0c;垃圾回收是一种自动管理内存的机制&#xff0c;它负责识别不再被程序引用的对象并释放其占用的内存…

HarmonyOS与AbilitySlice路由配置

上一章我有教到鸿蒙应用开发——Ability鸿蒙应用开发的基础知识&#xff0c;那么今天我们来讲一下AbilitySlice路由配置 AbilitySlice路由配置 虽然一个Page可以包含多个AbilitySlice&#xff0c;但是Page进入前台时界面默认只展示一个AbilitySlice。默认展示的AbilitySlice是…

Java+SSM springboot+MySQL家政服务预约网站设计en24b

随着社区居民对生活品质的追求以及社会老龄化的加剧&#xff0c;社区居民对家政服务的需求越来越多&#xff0c;家政服务业逐渐成为政府推动、扶持和建设的重点行业。家政服务信息化有助于提高社区家政服务的工作效率和质量。 本次开发的家政服务网站是一个面向社区的家政服务网…

TCP首部格式_基本知识

TCP首部格式 表格索引: 源端口目的端口 序号 确认号 数据偏移保留 ACK等 窗口检验和紧急指针 TCP报文段首部格式图 源端口与目的端口: 各占16位 序号:占32比特&#xff0c;取值范围0~232-1。当序号增加到最后一个时&#xff0c;下一个序号又回到0。用来指出本TCP报文段数据载…

面试 Java 框架八股文十问十答第二期

面试 Java 框架八股文十问十答第二期 作者&#xff1a;程序员小白条&#xff0c;个人博客 相信看了本文后&#xff0c;对你的面试是有一定帮助的&#xff01; ⭐点赞⭐收藏⭐不迷路&#xff01;⭐ 1.AOP的术语&#xff0c;以及两种动态代理实现方法&#xff0c;以及它们的区别…

Notepad++批量添加引号

工作中经常会遇到这样情景&#xff1a;业务给到一批订单号&#xff0c;需要查询这批订单的某些字段信息。在where条件中需要传入这些订单号的数组&#xff0c;并且订单号用引号引起&#xff0c;用引号隔开。 字符串之间长度相同 可以按住CtrlAlt和鼠标左键选中区域&#xff0…

手持式安卓主板_PDA安卓板_智能手持终端方案

手持式安卓主板方案是一种智能终端设备&#xff0c;具备自动对焦和闪光灯功能&#xff0c;可以在昏暗的环境下快速扫描二维码并轻松采集数据。该方案还提供多渠道支付和数据采集功能&#xff0c;为用户提供了便捷的体验。 该方案的产品基于手持式安卓主板&#xff0c;并搭载了八…

基于ROPNet项目训练modelnet40数据集进行3d点云的配置

项目地址&#xff1a; https://github.com/zhulf0804/ROPNet 在 MVP Registration Challenge (ICCV Workshop 2021)&#xff08;ICCV Workshop 2021&#xff09;中获得了第二名。项目可以在win10环境下运行。 论文地址&#xff1a; https://arxiv.org/abs/2107.02583 网络简介…

基于H5“汉函谷关起点新安县旅游信息系统”设计与实现

目 录 摘 要 1 ABSTRACT 2 第1章 绪论 3 1.1 系统开发背景及意义 3 1.2 系统开发的目标 3 第2章 主要开发技术介绍 5 2.1 H5技术介绍 5 2.2 Visual Studio 技术介绍 5 2.3 SQL Server数据库技术介绍 6 第3章 系统分析与设计 7 3.1 可行性分析 7 3.1.1 技术可行性 7 3.1.2 操作…

HTTP请求

前言 HTTP是应用层的一个协议。实际我们访问一个网页&#xff0c;都会像该网页的服务器发送HTTP请求&#xff0c;服务器解析HTTP请求&#xff0c;返回HTTP响应。如此就是我们获取资源或者上传资源的原理 HTTP请求报头格式 图片来自网络 HTTP请求报头总体有四部分&#xff1a;…

pycharm中绘制一个3D曲线

import numpy as np import matplotlib.pyplot as plt # 中文的设置 import matplotlib as mp1 from mpl_toolkits.mplot3d import Axes3D mp1.rcParams["font.sans-serif"] ["kaiti"] mp1.rcParams["axes.unicode_minus"] False # 数据创建 X…

忽略python运行出现的大量警告

添加以下代码即可 import warnings warnings.filterwarnings(ignore)

制作红木家具3d模型

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 在家居行业中&#xff0c;设计师可以通过在3D建模中添加实际的家具、…

【python】包(package)与模块(module)、import、__name__与__main__

导入模块一般写在程序最前面&#xff0c;且顺序为&#xff1a;内置模块、第三方模块、自定义模块 一、模块&#xff08;module&#xff09;与包&#xff08;package&#xff09; 模块&#xff08;module&#xff09;可以理解为是一个.py文件&#xff0c;import 模块 相当于执行…

应用于智慧园区的AI边缘计算盒子+AI算法软硬一体化方案

工业园区多为生产型和物流型企业&#xff0c;劳动人员密集&#xff0c;外来人口多&#xff0c;农民工多&#xff0c;人员流动大&#xff0c;车流量大&#xff0c;易引发车祸、破坏公共设施和绿化工程等案件; 英码智慧园区方案&#xff0c;可实现100%管理所有出入人员&#xff1…