1.公共字段自动填充
问题
有些字段,如 : create_time,create_user , update_time , update_user是公共的,每次赋值都要重新编写代码,会造成代码冗余 ;
序号 | 字段名 | 含义 | 数据类型 |
---|---|---|---|
1 | create_time | 创建时间 | datetime |
2 | create_user | 创建人id | bigint |
3 | update_time | 修改时间 | datetime |
4 | update_user | 修改人id | bigint |
实现 :
-
自定义注解AutoFill,用于表示需要进行公共自读那自动填充的方法
-
自定义切面类,统一拦截加入了AutoFill注解的方法,通过反射为公共字段赋值。
-
在Mapper的方法上加入AutoFill注解。
1.创建枚举类
在common下定义了需要加上注解的数据库操作类型 :
OperationType.java :
package com.sky.enumeration; /** * 数据库操作类型 */ public enum OperationType { /** * 更新操作 */ UPDATE, /** * 插入操作 */ INSERT }
2.自定义注解AutoFill
package com.sky.annotation; import com.sky.enumeration.OperationType; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自定义注解 , 用于标识某个方法需要进行 公共字段 自动填充处理 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AutoFill { // 指定数据库操作类型 OperationType value(); }
3.自定义拦截器
package com.sky.aspect; import com.sky.annotation.AutoFill; import com.sky.constant.AutoFillConstant; import com.sky.context.BaseContext; import com.sky.enumeration.OperationType; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.time.LocalDateTime; /** * 自定义切面类 , 实现公共字段自动填充处理逻辑 */ @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(); // 获取数据库操作类型 // 获取当前被拦截的方法的参数 -- 实体对象(比如employee) 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个公共字段赋值 -- 通过反射来进行赋值 // 获取Set方法 try { // Method setCreateTime = entity.getClass().getDeclaredMethod("setCreateTime" , LocalDateTime.class); // Method setCreateUser = entity.getClass().getDeclaredMethod("setCreateUser" , Long.class); // Method setUpdateTime = entity.getClass().getDeclaredMethod("setUpdateTime", LocalDateTime.class); // Method setUpdateUser = entity.getClass().getDeclaredMethod("setUpdateUser", Long.class); // 用定义好的常量实现 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){ // 为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(); } } } }
注意 :
-
切面是 切入点 + 通知
4.在mapper对应方法上加上注解 :
@AutoFill(value = OperationType.INSERT) void insert(Employee employee);
@AutoFill(value = OperationType.UPDATE) void update(Employee employee);
CategoryMapper中一样 ;
5.在impl中将之前写的insert和update方法里对公共字段进行赋值的语句注释掉
2.新增菜品
2.1 需求分析与设计
2.1.1 产品原型
后台系统中可以管理菜品信息,通过 新增功能来添加一个新的菜品,在添加菜品时需要选择当前菜品所属的菜品分类,并且需要上传菜品图片。
新增菜品原型:
当填写完表单信息, 点击"保存"按钮后, 会提交该表单的数据到服务端, 在服务端中需要接受数据, 然后将数据保存至数据库中。
业务规则:
-
菜品名称必须是唯一的
-
菜品必须属于某个分类下,不能单独存在
-
新增菜品时可以根据情况选择菜品的口味
-
每个菜品必须对应一张图片
2.1.2 接口设计
根据上述原型图先粗粒度设计接口,共包含3个接口。
接口设计:
-
根据类型查询分类(已完成)
-
文件上传
-
新增菜品
接下来细粒度分析每个接口,明确每个接口的请求方式、请求路径、传入参数和返回值。
1. 根据类型查询分类
2. 文件上传
3. 新增菜品
2.1.3 表设计
通过原型图进行分析:
新增菜品,其实就是将新增页面录入的菜品信息插入到dish表,如果添加了口味做法,还需要向dish_flavor表插入数据。所以在新增菜品时,涉及到两个表:
表名 | 说明 |
---|---|
dish | 菜品表 |
dish_flavor | 菜品口味表 |
1). 菜品表:dish
字段名 | 数据类型 | 说明 | 备注 |
---|---|---|---|
id | bigint | 主键 | 自增 |
name | varchar(32) | 菜品名称 | 唯一 |
category_id | bigint | 分类id | 逻辑外键 |
price | decimal(10,2) | 菜品价格 | |
image | varchar(255) | 图片路径 | |
description | varchar(255) | 菜品描述 | |
status | int | 售卖状态 | 1起售 0停售 |
create_time | datetime | 创建时间 | |
update_time | datetime | 最后修改时间 | |
create_user | bigint | 创建人id | |
update_user | bigint | 最后修改人id |
2). 菜品口味表:dish_flavor
字段名 | 数据类型 | 说明 | 备注 |
---|---|---|---|
id | bigint | 主键 | 自增 |
dish_id | bigint | 菜品id | 逻辑外键 |
name | varchar(32) | 口味名称 | |
value | varchar(255) | 口味值 |
2.2 代码开发
2.2.1 文件上传实现
因为在新增菜品时,需要上传菜品对应的图片(文件),包括后绪其它功能也会使用到文件上传,故要实现通用的文件上传接口。
文件上传,是指将本地图片、视频、音频等文件上传到服务器上,可以供其他用户浏览或下载的过程。文件上传在项目中应用非常广泛,我们经常发抖音、发朋友圈都用到了文件上传功能。
实现文件上传服务,需要有存储的支持,那么我们的解决方案将以下几种:
-
直接将图片保存到服务的硬盘(springmvc中的文件上传)
-
优点:开发便捷,成本低
-
缺点:扩容困难
-
-
使用分布式文件系统进行存储
-
优点:容易实现扩容
-
缺点:开发复杂度稍大(有成熟的产品可以使用,比如:FastDFS,MinIO)
-
-
使用第三方的存储服务(例如OSS)
-
优点:开发简单,拥有强大功能,免维护
-
缺点:付费
-
在本项目选用阿里云的OSS服务进行文件存储。(前面课程已学习过阿里云OSS,不再赘述)
实现步骤:
1). 定义OSS相关配置
在sky-server模块
application-dev.yml
sky: alioss: endpoint: oss-cn-hangzhou.aliyuncs.com access-key-id: LTAI5tPeFLzsPPT8gG3LPW64 access-key-secret: U6k1brOZ8gaOIXv3nXbulGTUzy6Pd7 bucket-name: sky-take-out
application.yml
spring: profiles: active: dev #设置环境 sky: alioss: endpoint: ${sky.alioss.endpoint} access-key-id: ${sky.alioss.access-key-id} access-key-secret: ${sky.alioss.access-key-secret} bucket-name: ${sky.alioss.bucket-name}
注意 :
-
这里在sky-commom下的properties中的AliOssProperties中配置类中设置了这四个属性 ,封装成了一个java对象 :
package com.sky.properties; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "sky.alioss") // 配置属性类 , 读取配置文件的配置项封装成一个java对象 @Data public class AliOssProperties { private String endpoint; private String accessKeyId; private String accessKeySecret; private String bucketName; }
在这两个之间对于属性的书写不一样,如 : yml中的bucket-name,在AliOssProperties中就是bucketName,采取的是驼峰命名法,这里在spring框架中能够自动进行转换 ;在yml中习惯使用横线来分割单词;
-
在yml中配置直接引用dev中的值,在dev中配置相应的值 ;
2). 读取OSS配置
在sky-common模块中,已定义
package com.sky.properties; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "sky.alioss") @Data public class AliOssProperties { private String endpoint; private String accessKeyId; private String accessKeySecret; private String bucketName; }
3). 生成OSS工具类对象
在sky-server模块
package com.sky.config; import com.sky.properties.AliOssProperties; import com.sky.utils.AliOssUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 配置类,用于创建AliOssUtil对象 */ @Configuration @Slf4j public class OssConfiguration { @Bean @ConditionalOnMissingBean public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){ log.info("开始创建阿里云文件上传工具类对象:{}",aliOssProperties); return new AliOssUtil(aliOssProperties.getEndpoint(), aliOssProperties.getAccessKeyId(), aliOssProperties.getAccessKeySecret(), aliOssProperties.getBucketName()); } }
其中,AliOssUtil.java已在sky-common模块中定义
package com.sky.utils; import com.aliyun.oss.ClientException; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.OSSException; import lombok.AllArgsConstructor; import lombok.Data; import lombok.extern.slf4j.Slf4j; import java.io.ByteArrayInputStream; @Data @AllArgsConstructor @Slf4j public class AliOssUtil { private String endpoint; private String accessKeyId; private String accessKeySecret; private String bucketName; /** * 文件上传 * * @param bytes * @param objectName * @return */ public String upload(byte[] bytes, String objectName) { // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); try { // 创建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()); } finally { if (ossClient != null) { ossClient.shutdown(); } } //文件访问路径规则 https://BucketName.Endpoint/ObjectName StringBuilder stringBuilder = new StringBuilder("https://"); stringBuilder .append(bucketName) .append(".") .append(endpoint) .append("/") .append(objectName); log.info("文件上传到:{}", stringBuilder.toString()); return stringBuilder.toString(); } }
4). 定义文件上传接口
在sky-server模块中定义接口
package com.sky.controller.admin; import com.sky.constant.MessageConstant; import com.sky.result.Result; import com.sky.utils.AliOssUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.util.UUID; /** * 通用接口 */ @RestController @RequestMapping("/admin/common") @Api(tags = "通用接口") @Slf4j public class CommonController { @Autowired private AliOssUtil aliOssUtil; /** * 文件上传 * @param file * @return */ @PostMapping("/upload") @ApiOperation("文件上传") public Result<String> upload(MultipartFile file){ log.info("文件上传:{}",file); try { //原始文件名 String originalFilename = file.getOriginalFilename(); //截取原始文件名的后缀 dfdfdf.png String 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); } }
2.2.2 新增菜品实现
1). 设计DTO类
在sky-pojo模块中
package com.sky.dto; import com.sky.entity.DishFlavor; import lombok.Data; import java.io.Serializable; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; @Data public class DishDTO implements Serializable { private Long id; //菜品名称 private String name; //菜品分类id private Long categoryId; //菜品价格 private BigDecimal price; //图片 private String image; //描述信息 private String description; //0 停售 1 起售 private Integer status; //口味 private List<DishFlavor> flavors = new ArrayList<>(); }
2). Controller层
进入到sky-server模块
package com.sky.controller.admin; import com.sky.dto.DishDTO; import com.sky.dto.DishPageQueryDTO; import com.sky.entity.Dish; import com.sky.result.PageResult; import com.sky.result.Result; import com.sky.service.DishService; import com.sky.vo.DishVO; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Set; /** * 菜品管理 */ @RestController @RequestMapping("/admin/dish") @Api(tags = "菜品相关接口") @Slf4j public class DishController { @Autowired private DishService dishService; /** * 新增菜品 * * @param dishDTO * @return */ @PostMapping @ApiOperation("新增菜品") public Result save(@RequestBody DishDTO dishDTO) { log.info("新增菜品:{}", dishDTO); dishService.saveWithFlavor(dishDTO);//后绪步骤开发 return Result.success(); } }
3). Service层接口
package com.sky.service; import com.sky.dto.DishDTO; import com.sky.entity.Dish; public interface DishService { /** * 新增菜品和对应的口味 * * @param dishDTO */ public void saveWithFlavor(DishDTO dishDTO); }
4). Service层实现类
package com.sky.service.impl; @Service @Slf4j public class DishServiceImpl implements DishService { @Autowired private DishMapper dishMapper; @Autowired private 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);//后绪步骤实现 } } }
注意 :
-
@Transactional 表示该方法时原子性的,要么全成功,要么全失败,在涉及多个表的操作时加上,要开启注解方式的事务控制,在启动类上已经开启过了 ;(在启动类上加上@EnableTransactionManagement //开启注解方式的事务管理)
5). Mapper层
DishMapper.java中添加
/** * 插入菜品数据 * * @param dish */ @AutoFill(value = OperationType.INSERT) void insert(Dish dish);
在/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>
-
这里通过useGeneratedKeys="true" keyProperty="id"给后面在impl中获取id值;
DishFlavorMapper.java
package com.sky.mapper; import com.sky.entity.DishFlavor; import java.util.List; @Mapper public interface DishFlavorMapper { /** * 批量插入口味数据 * @param flavors */ void insertBatch(List<DishFlavor> flavors); }
在/resources/mapper中创建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>
2.3 功能测试
进入到菜品管理--->新建菜品
由于没有实现菜品查询功能,所以保存后,暂且在表中查看添加的数据。
测试成功。
3.菜品分类查询
1.需求分析
业务规则 :
-
根据页码展示菜品信息
-
每页展示10条数据
-
分页查询时可以根据需要输入菜品名称,菜品分类,菜品状态进行查询
接口设计 :
-
Path : /admin/dish/page
-
Method : GET
-
请求参数 :
-
page : 必须 : 页码
-
pageSize : 必须 : 每页记录数
-
name : 非必须 : 菜品名称
-
categoryId : 非必须 : 分类id
-
status : 非必须 : 菜品售卖状态
-
-
返回数据:
注意 : 其中菜品表中只存了分类的id,没有存categoryName,所以要查分类表
2.代码开发
1.设计DTO :
根据菜品分业务查询接口定义设计对应的DTO
package com.sky.dto; import lombok.Data; import java.io.Serializable; @Data public class DishPageQueryDTO implements Serializable { private int page; private int pageSize; private String name; //分类id private Integer categoryId; //状态 0表示禁用 1表示启用 private Integer status; }
2.设计VO
根据菜品分页查询接口定义设计对应的VO
在VO中扩展了categoryName属性 ;
package com.sky.vo; import com.sky.entity.DishFlavor; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @Data @Builder @NoArgsConstructor @AllArgsConstructor public class DishVO implements Serializable { private Long id; //菜品名称 private String name; //菜品分类id private Long categoryId; //菜品价格 private BigDecimal price; //图片 private String image; //描述信息 private String description; //0 停售 1 起售 private Integer status; //更新时间 private LocalDateTime updateTime; //分类名称 private String categoryName; //菜品关联的口味 private List<DishFlavor> flavors = new ArrayList<>(); //private Integer copies; }
直接把vo交给前端 ;
3.controller
/** * 菜品分页查询 * @param dishPageQueryDTO * @return */ @GetMapping("/page") @ApiOperation("菜品分页查询") public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO){// 不是json格式,是querry地址栏传参格式,不用加@RequestBody注解 log.info("菜品分页查询 : {}",dishPageQueryDTO); // 调用service方法 , 用PageResult接收 PageResult pageResult = dishService.pageQuery(dishPageQueryDTO); return Result.success(pageResult) ; }
4.service层
/** * 菜品分页查询 * @param dishPageQueryDTO * @return */ PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO);
5.impl :
/** * 菜品分页查询 * @param dishPageQueryDTO * @return */ @Override public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) { PageHelper.startPage(dishPageQueryDTO.getPage(),dishPageQueryDTO.getPageSize()); Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO); return new PageResult(page.getTotal(),page.getResult()); }
注意 :
-
这里使用分页插件PageHelper,原理是ThreadLocal ;
6.Mapper
/** * 菜品分页查询 * @param dishPageQueryDTO * @return */ Page<DishVO> pageQuery(DishPageQueryDTO dishPageQueryDTO);
7.xml
<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>
这里使用分页查询 ,用动态sql来拼接可能为空的属性;
4.删除菜品
1.需求设计与分析
-
产品原型 :
删除菜品
-
业务规则 :
-
可以一次删除一个菜品 , 也可以批量删除菜品
-
起售中的菜品不能删除
-
被套餐关联的菜品不能删除
-
删除菜品后,关联的口味数据也需要删除
-
-
接口设计 :
批量删除 : 可以只设计一个接口
Path : /admin/dish
Method : Delete
请求参数 : query
-
ids : 必须 : 菜品id,之间用都好分割
返回数据 :
-
code : 必须
-
data : 非必须
-
msg : 非必须
-
2.代码开发
controller
/** * 菜品批量删除 * @param ids * @return */ @DeleteMapping @ApiOperation("菜品批量删除") public Result delete(@RequestParam List<Long> ids){//使用mvc框架将用,分割ids中的id数据封装到List集合中去 log.info("菜品批量删除 , {}",ids); dishService.deleteBatch(ids) ;// 后续步骤实现 return Result.success(); }
service
/** * 菜品批量删除 * @param ids */ void deleteBatch(List<Long> ids);
impl
/** * 菜品批量删除 * @param ids */ @Transactional // 事务注解 public void deleteBatch(List<Long> ids) { // 判断当前菜品是否能够删除 --- 是否存在起售中的菜品? // 根据status for(Long id : ids){ Dish dish = dishMapper.getById(id); if(dish.getStatus() == StatusConstant.ENABLE){ // 当前菜品在起售中 , 不能删除 , 直接抛出异常 throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE) ; } } // 判断当前菜品是否能够删除 --- 是否被套餐关联了? // 获取setmealIds 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);//后绪步骤实现 // } // 根据菜品id集合批量删除菜品数据 // sql : delete from dish where id in (?,?,?) dishMapper.deleteByIds(ids); // 根据菜品id集合批量删除口味数据 // sql : delete from dish_flavor where dish_id in (?,?,?) dishFlavorMapper.deleteByDishIds(ids); }
Mapper
在DishMapper中声明getById方法,并配置SQL:
/** * 根据主键查询菜品 * * @param id * @return */ @Select("select * from dish where id = #{id}") Dish getById(Long id);
创建SetmealDishMapper,声明getSetmealIdsByDishIds方法,并在xml文件中编写SQL:
package com.sky.mapper; import com.sky.entity.SetmealDish; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Mapper; import java.util.List; @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:
/** * 根据主键删除菜品数据 * * @param id */ @Delete("delete from dish where id = #{id}") void deleteById(Long id);
在DishFlavorMapper中声明deleteByDishId方法并配置SQL:
/** * 根据菜品id删除对应的口味数据 * @param dishId */ @Delete("delete from dish_flavor where dish_id = #{dishId}") void deleteByDishId(Long dishId);
/** * 根据菜品id查找菜品 * @param id * @return */ @Select("select * from dish where id = #{id}") Dish getById(Long id);
优化 :
-
在删除菜品和相关口味时,遍历的方式会多次访问数据库,这样造成性能的消耗,所以可以采用批量删除的操作来优化
// 根据菜品id集合批量删除菜品数据 // sql : delete from dish where id in (?,?,?) dishMapper.deleteByIds(ids); // 根据菜品id集合批量删除口味数据 // sql : delete from dish_flavor where dish_id in (?,?,?) dishFlavorMapper.deleteByDishIds(ids);
DishMapper :
/** * 根据菜品id集合批量删除菜品数据 */ void deleteByIds(List<Long> ids);
DishFlavorMapper :
/** * 根据菜品id集合批量删除关联的口味数据 * @param dishIds */ void deleteByDishIds(List<Long> dishIds);
DishMapper.xml :
<delete id="deleteByIds"> delete from dish where id in <foreach collection="ids" open="(" close=")" separator="," item="id"> #{id} </foreach> </delete>
DishFlavorMapper.xml :
<delete id="deleteByDishIds"> delete from dish_flavor where dish_id in <foreach collection="dishIds" open="(" close=")" separator="," item="dishId"> #{dishId} </foreach> </delete>
5.修改菜品
1.需求设计与分析
接口设计 :
-
根据id查询菜品(用于回显数据)
-
根据类型查询分类(已实现)
-
文件上传(已实现)
-
修改菜品
2.代码开发
1). Controller层
根据id查询菜品的接口定义在DishController中创建方法:
/** * 根据id查询菜品 * * @param id * @return */ @GetMapping("/{id}") @ApiOperation("根据id查询菜品") public Result<DishVO> getById(@PathVariable Long id) { log.info("根据id查询菜品:{}", id); DishVO dishVO = dishService.getByIdWithFlavor(id);//后绪步骤实现 return Result.success(dishVO); }
2). Service层接口
在DishService接口中声明getByIdWithFlavor方法:
/** * 根据id查询菜品和对应的口味数据 * * @param id * @return */ DishVO getByIdWithFlavor(Long id);
3). Service层实现类
在DishServiceImpl中实现getByIdWithFlavor方法:
/** * 根据id查询菜品和对应的口味数据 * * @param id * @return */ public DishVO getByIdWithFlavor(Long id) { //根据id查询菜品数据 Dish dish = dishMapper.getById(id); //根据菜品id查询口味数据 List<DishFlavor> dishFlavors = dishFlavorMapper.getByDishId(id);//后绪步骤实现 //将查询到的数据封装到VO DishVO dishVO = new DishVO(); BeanUtils.copyProperties(dish, dishVO); dishVO.setFlavors(dishFlavors); return dishVO; }
4). Mapper层
在DishFlavorMapper中声明getByDishId方法,并配置SQL:
/** * 根据菜品id查询对应的口味数据 * @param dishId * @return */ @Select("select * from dish_flavor where dish_id = #{dishId}") List<DishFlavor> getByDishId(Long dishId);
5.2.1 修改菜品实现
1). Controller层
根据修改菜品的接口定义在DishController中创建方法:
/** * 修改菜品 * * @param dishDTO * @return */ @PutMapping @ApiOperation("修改菜品") public Result update(@RequestBody DishDTO dishDTO) { log.info("修改菜品:{}", dishDTO); dishService.updateWithFlavor(dishDTO); return Result.success(); }
2). Service层接口
在DishService接口中声明updateWithFlavor方法:
/** * 根据id修改菜品基本信息和对应的口味信息 * * @param dishDTO */ void updateWithFlavor(DishDTO dishDTO);
3). Service层实现类
在DishServiceImpl中实现updateWithFlavor方法:
/** * 根据id修改菜品基本信息和对应的口味信息 * * @param dishDTO */ 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); } }
4). Mapper层
在DishMapper中,声明update方法:
/** * 根据id动态修改菜品数据 * * @param dish */ @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>