黑马苍穹外卖学习Day3

目录

  • 公共字段自动填充
    • 问题分析
    • 实现思路
    • 代码实现
  • 新增菜品
    • 需求分析和设计
    • 接口设计
    • 代码开发
      • 开发文件上传接口
      • 功能开发
  • 菜品分页查询
    • 需求分析和设计
    • 代码开发
  • 菜品删除功能
    • 需求分析与设计
    • 代码实现
    • 代码优化
  • 修改菜品
    • 需求分析和设计
    • 代码实现

公共字段自动填充

问题分析

员工表和分类表中公共字段可以优化,代码冗余后期修改时麻烦。
在这里插入图片描述

实现思路

在这里插入图片描述
下面我们来重温一下用到的技术点

反射的作用:当我们编写程序时,通常我们在编译时就知道类的结构,可以直接使用类的方法和字段。但有些时候,我们可能希望在程序运行时,根据一些条件来决定使用哪个类,或者根据类的信息执行一些操作。这时候,反射就派上用场了。
简单来说,反射允许我们在程序运行时动态地了解和操作类的信息,比如创建对象、调用方法、访问字段等。这样我们就能够在不预先知道类结构的情况下,通过代码来处理和使用类。反射提供了一种动态性,但也需要注意使用时可能带来的性能损耗和一些潜在的安全问题。

其主要作用表现为:
动态加载类:
允许在运行时根据条件动态加载类,而不需要在编译时确定要加载的确切类。
动态创建对象:
允许在运行时通过类名创建对象实例,而不需要在编译时知道确切的类。
获取类的信息:
提供了获取类的各种信息(类名、字段、方法等)的能力,使得在运行时可以动态地了解类的结构。
调用方法:
允许在运行时通过方法名调用类的方法,包括私有方法。
访问和修改字段:
允许在运行时访问和修改类的字段,包括私有字段。
实现通用框架和库:
一些通用的框架和库,如 Spring 框架,利用反射处理用户定义的类和对象,提供了高度的灵活性和可扩展性。
注解处理:
允许在运行时获取类上的注解信息,并根据注解执行相应的逻辑。
动态代理:
提供了实现动态代理的能力,使得可以在运行时创建代理对象并拦截对这些代理对象的方法调用。

AOP的作用:
AOP 是一种编程范式,旨在通过将横切关注点(cross-cutting concerns)从主要业务逻辑中分离出来,使代码更模块化、易维护。横切关注点是那些涉及多个模块、不容易用传统的面向对象方法解决的问题,比如日志记录、事务管理、性能优化等。
在 AOP 中,横切关注点被封装成一个切面(Aspect),而切面是一组连接点(Join Point)和通知(Advice)的集合。连接点是在应用执行过程中可以插入切面的点,通知是在连接点上执行的代码。AOP 提供了一种将切面与主要业务逻辑分开的方式,使代码更清晰,易于维护。
AOP与反射的关联:
在某些 AOP 框架中,反射被用于实现切面的动态织入。织入是将切面与应用的主要业务逻辑结合的过程。AOP 框架通过使用反射来动态创建代理对象,将切面的代码插入到连接点上,从而实现在运行时对应用进行横切关注点的处理。
综合来看,AOP 与反射可以协同工作,通过反射来实现 AOP 中的横切关注点的动态织入。反射提供了在运行时获取和操作类信息的能力,而 AOP 则通过切面的概念将这些横切关注点模块化,使代码更易于维护和理解。

代码实现

新建注解

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

新建切面类

/**
 * 自定义切面,实现公共字段自动填充
 */
@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) throws NoSuchMethodException {
        //进行公共字段赋值
        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){
            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){
            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();
            }
        }
        
    }
}

给各Mapper接口加上定义的注解

    @AutoFill(value = OperationType.UPDATE)
    void update(Employee employee);
    @Insert("insert into employee(name, username, password, phone, sex, id_number, status, create_time, update_time, create_user, update_user) " +
            "values " +
            "(#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{status},#{createTime},#{updateTime},#{createUser},#{updateUser})")
    @AutoFill(value = OperationType.INSERT)
    void insert(Employee employee);    

另外对应前文在各Service实现类里为公共字段赋值语句全部可以省略,自此完成该功能。

新增菜品

需求分析和设计

在这里插入图片描述
在这里插入图片描述

接口设计

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码开发

开发文件上传接口

使用阿里云储存桶进行开发。
在这里插入图片描述
在application.yml中设置

sky:
  jwt:
    # 设置jwt签名加密时使用的秘钥
    admin-secret-key: itcast
    # 设置jwt过期时间
    admin-ttl: 72000000
    # 设置前端传递过来的令牌名称
    admin-token-name: token
  alioss:
    access-key-id: ${sky.alioss.access-key-id}
    access-key-secret: ${sky.alioss.access-key-secret}
    bucket-name: ${sky.alioss.bucket-name}
    endpoint: ${sky.alioss.endpoint}

在application-dev中配置具体参数(自己的OSS信息)

  alioss:
    endpoint: oss-cn-nanjing.aliyuncs.com
    access-key-id: xxxxxxx
    access-key-secret: xxxxxxx
    bucket-name: xxxxxxxx
@Component
@ConfigurationProperties(prefix = "sky.alioss")
@Data
public class AliOssProperties {

    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;

}
/**
 * 配置类,用于创建AliOssUtil对象
 */
@Configuration
@Slf4j
public class OssConfiguration {
    @Bean
    public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){
        log.info("开始创建阿里云上传工具对象:{}",aliOssProperties);
        return new AliOssUtil(aliOssProperties.getEndpoint(),
                aliOssProperties.getAccessKeyId(),
                aliOssProperties.getAccessKeySecret(),
                aliOssProperties.getBucketName());
    }
}

这两段代码逻辑
OssConfiguration 这个配置类的主要作用是创建并配置 AliOssUtil 这个 Bean 对象,为其构造函数所需的参数赋值。具体来说:

  • 通过 @Bean 注解,aliOssUtil 方法被标记为一个 Bean 的生产方法,它会被 Spring 容器调用,用于创建一个 AliOssUtil 的实例。
  • 方法的参数 AliOssProperties aliOssProperties 是通过依赖注入的方式得到的,Spring 容器会负责注入 AliOssProperties 对象。这个对象是通过 @ConfigurationProperties 注解读取配置文件中的属性,并映射到 AliOssProperties 类的实例中。
  • 在方法体中,通过 aliOssProperties 对象获取了一些配置信息,然后使用这些信息创建了 AliOssUtil 的实例,并返回给 Spring 容器。
    所以,你可以把这个配置类看作是一个对象的装配工厂,负责创建和配置应用程序中需要用到的对象(这里是 AliOssUtil),并将这些对象注册到 Spring 容器中,方便其他地方进行使用。

新建一个Conroller层

package com.sky.controller.admin;

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
     */
    @ApiOperation("文件上传")
    @PostMapping("/upload")
    public Result<String> upload(MultipartFile file){
        log.info("文件上传,{}",file);
        try {
            //原始文件名
            String originalFilename = file.getOriginalFilename();
            //截取原始文件后缀
            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.success();
    }

}

功能开发

新建DishController

package com.sky.controller.admin;

import com.sky.dto.DishDTO;
import com.sky.result.Result;
import com.sky.service.DishService;
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.*;

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


}

Service实现类

package com.sky.service.impl;

import com.sky.dto.DishDTO;
import com.sky.entity.Dish;
import com.sky.entity.DishFlavor;
import com.sky.mapper.DishFlavorMapper;
import com.sky.mapper.DishMapper;
import com.sky.service.DishService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Slf4j
public class DishServiceImpl implements DishService {
    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private DishFlavorMapper dishFlavorMapper;

    /**
     * 新增菜品和对应口味
     * @param dishDTO
     */
    @Override
    @Transactional
    public void saveWithFlavor(DishDTO dishDTO) {
        Dish dish=new Dish();
        BeanUtils.copyProperties(dishDTO, dish);
        //向菜品表插入一条数据并没有口味,所以不需要DTO
        dishMapper.insert(dish);
        //获取insert语句生成的主键值
        Long dishId=dish.getId();
        //向口味表插入N条数据,一道菜的口味可能有很多
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if(flavors != null && flavors.size()>0){
            //为菜品的dishId赋值,防止后面插入空值
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dishId);
            });
            //在if里面插入多条数据
            //sql支持批量插入
            dishFlavorMapper.insertBatch(flavors);

        }
    }
}

因为除了有菜品数据库还有对应的口味数据库,为解耦合需要定义两个Mapper文件。
Mapper接口类

package com.sky.mapper;

import com.sky.annotation.AutoFill;
import com.sky.dto.DishDTO;
import com.sky.entity.Dish;
import com.sky.enumeration.OperationType;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface DishMapper {
    /**
     * 根据分类id查询菜品数量
     * @param categoryId
     * @return
     */
    @Select("select count(id) from dish where category_id = #{categoryId}")
    Integer CountByCategoryId(Long categoryId);

    /**
     * 插入菜品数据
     * @param dish
     */
    @AutoFill(value = OperationType.INSERT)
    void insert(Dish dish);
}

package com.sky.mapper;

import com.sky.entity.DishFlavor;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface DishFlavorMapper {
    /**
     * 批量插入口味数据
     * @param flavors
     */
    void insertBatch(List<DishFlavor> flavors);
}

Mapper类

<?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语句执行完后产生的主键值会赋值给dish的id-->
    <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>

<?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>

菜品分页查询

需求分析和设计

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码开发

注意联表查询

Controller层

    /**
     * 菜品分页查询
     * @param dishPageQueryDTO
     * @return
     */
    @GetMapping("/page")
    @ApiOperation("菜品分页查询")
    //通过URL传参,并不是json格式故不需要加@RequestBody
    public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO){
        log.info("菜品分页查询:{}",dishPageQueryDTO);
        PageResult pageResult=dishService.pageQuery(dishPageQueryDTO);
        return Result.success(pageResult);
    }

Service实现类

    /**
     * 菜品分页查询
     * @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());
    }

Mapper层以及xml

    /**
     * 菜品分页查询
     * @param dishPageQueryDTO
     * @return
     */
    Page<DishVO> pageQuery(DishPageQueryDTO dishPageQueryDTO);
    <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>

在此复习一下左外连接的链表查询

左外连接(Left Outer Join)是一种数据库查询操作,用于联接两个表并返回左表中的所有行以及与右表中匹配的行。如果在右表中找不到匹配的行,那么结果集中将包含右表中的列,但是这些列的值将为NULL。

SELECT *
FROM 表A
LEFT OUTER JOIN 表B ON 表A.共有列 = 表B.共有列;

在这里,共有列 是两个表中用于匹配的列。左外连接会返回表A中的所有行,同时匹配的表B中的行,如果没有匹配的行,那么右表中的列将包含NULL。

例如,假设有两个表 学生表 和 成绩表:

学生表 (Students):

学生ID姓名
1小明
2小红
3小刚

成绩表 (Grades):

学生ID科目成绩
1数学90
1英语85
3数学95

使用左外连接,可以得到一个包含所有学生以及其对应成绩(如果有的话)的结果集:

SELECT *
FROM 学生表
LEFT OUTER JOIN 成绩表 ON 学生表.学生ID = 成绩表.学生ID;

左外连接结果集:

学生表.学生ID学生表.姓名成绩表.学生ID成绩表.科目成绩表.成绩
1小明1数学90
1小明1英语85
2小红NULLNULLNULL
3小刚3数学95

结果集将包含所有学生,以及他们的成绩(如果有的话)。对于没有成绩的学生,成绩表中的列将为NULL。

菜品删除功能

需求分析与设计

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

代码实现

Controller层

    /**
     * 菜品批量删除
     * @param ids
     * @return
     * @RequestParam 用于从HTTP请求中提取参数的注解
     */
    @DeleteMapping
    @ApiOperation("菜品批量删除")
    public Result delete(@RequestParam List<Long> ids){
        log.info("菜品批量删除:{}", ids);
        dishService.deleteBatch(ids);
        return Result.success();
    }

Service实现类

    /** @Transactional 保证事务一致性
     * 菜品批量删除
     * @param ids
     */
    @Override
    @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.deleteDishId(id);
        }

    }

由于要关联菜品与套餐表需要多个Mapper

@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);
}
public interface DishFlavorMapper {
    /**
     * 批量插入口味数据
     * @param flavors
     */
    void insertBatch(List<DishFlavor> flavors);

    /**
     * 根据菜品Id删除对应口味数据
     * @param dishId
     */
    @Delete("delete from dish_flavor where dish_id = #{dishId}")
    void deleteDishId(Long dishId);
}
    /**
     * 根据主键查询菜品数据
     * @param id
     * @return
     */
    @Select("select * from dish where id = #{id}")
    Dish getById(Long id);

    /**
     * 根据主键删除菜品数据
     * @param id
     */
    @Delete("delete from dish where id = #{id}")
    void deleteById(Long id);
@Mapper
public interface DishMapper {
    /**
     * 根据主键查询菜品数据
     * @param id
     * @return
     */
    @Select("select * from dish where id = #{id}")
    Dish getById(Long id);

    /**
     * 根据主键删除菜品数据
     * @param id
     */
    @Delete("delete from dish where id = #{id}")
    void deleteById(Long id);
}
<?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>

代码优化

因为在删除时每次要执行两条删除的SQL语句,性能不佳,如果数量过多可能会造成卡顿
修改Service实现类

        //删除菜品表中的菜品数据
//        for (Long id: ids) {
//            dishMapper.deleteById(id);
//            //删除菜品中关联的口味数据
//            dishFlavorMapper.deleteDishId(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对应的xml文件

    <delete id="deleteByIds">
        delete from dish where id in
        <foreach collection="ids" open="(" close=")" separator="," item="id">
            #{id}
        </foreach>
    </delete>
    <delete id="deleteByDishIds">
        delete from dish_flavor where dish_id
        <foreach collection="dishIds" open="(" close=")" separator="," item="dishId">
            #{dishId}
        </foreach>
    </delete>

修改菜品

需求分析和设计

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码实现

Controller层

    /**
     * 根据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);
    }

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

Service层实现类

   /**
     * 根据id查询菜品和对应口味数据
     * @param id
     * @return
     */
    @Override
    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;
    }
    /**
     * 根据id修改菜品基本信息和口味信息
     * @param dishDTO
     */
    @Override
    public void updateWithFlavor(DishDTO dishDTO) {
        //修改菜品表基本信息
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);
        dishMapper.update(dish);
        //删除原有的口味数据
        dishFlavorMapper.deleteDishId(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层

    /**
     * 根据菜品id查询对应的口味数据
     * @param dishId
     * @return
     */
    @Select("select * from dish_flavor where dish_id = #{dishId}")
    List<DishFlavor> getByDishId(Long dishId);
    /**
     * 根据id来动态修改菜品
     * @param dish
     */
    @AutoFill(value = OperationType.UPDATE)
    void update(Dish dish);
    <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>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

洗地机哪种牌子好?智能洗地机排行

选择一款性能稳定、使用方便的洗地机&#xff0c;对于家庭清洁至关重要。近年来&#xff0c;随着懒人经济的兴起&#xff0c;智能家电不断涌现。特别是在家居清洁领域&#xff0c;人们追求更加轻松便捷的清洁体验。洗地机行业近年来迎来了快速增长&#xff0c;各大厂商竞相推出…

Java学习笔记(六)——基本数据类型及其对应的包装类

文章目录 包装类基本数据类型及其对应的包装类获取Integer对象的方式(了解)获取Integer对象两种方式的区别(掌握) 包装类的计算&#xff1a;自动装箱和自动拆箱Integer成员方法综合练习练习1练习2练习3练习4练习5 包装类 包装类&#xff1a;基本数据类型对应的引用数据类型。 …

基于ssm的常见小儿疾病中医护理系统的设计+jsp论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本小儿疾病中医护理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据…

一款完整的单节锂离子电池采用恒定电流/恒定电压线性充电器

一、基本概述 TX5806是一款完整的单节锂离子电池采用恒定电流/恒定电压线性充电器。芯片外部元件少&#xff0c;使芯片成为便携式应用的理想选择。芯片可以适合 USB 电源和适配器电源工作。由于采用了内部P-MOS架构&#xff0c;加上防倒充电路&#xff0c;所以不需要外部隔离二…

大创项目推荐 深度学习大数据物流平台 python

文章目录 0 前言1 课题背景2 物流大数据平台的架构与设计3 智能车货匹配推荐算法的实现**1\. 问题陈述****2\. 算法模型**3\. 模型构建总览 **4 司机标签体系的搭建及算法****1\. 冷启动**2\. LSTM多标签模型算法 5 货运价格预测6 总结7 部分核心代码8 最后 0 前言 &#x1f5…

基于SSM图书管理系统【源码】【最详细运行文档】

SSM图书管理系统【源码】【最详细运行文档】 系统简介系统涉及系统运行系统演示源码获取 系统简介 以往的图书馆管理事务处理主要使用的是传统的人工管理方式&#xff0c;这种管理方式存在着管理效率低、操作流程繁琐、保密性差等缺点&#xff0c;长期的人工管理模式会产生大量…

k8s的集群调度

k8s的集群调度: scheduler: 负责调度资源&#xff0c;把pod调度到node节点。 预算策略 优先策略 List-watch k8s集群当中,通过list-watch的机制进行每个组件的协作&#xff0c;保持数据同步,每个组件之间的解耦。 kubectl配置文件&#xff0c;向APIserver发送命令---apiserve…

解压方法之一 tar

文章目录 解压方法之一 tar语法压缩文件查看压缩文件的内容解压文件更多信息 解压方法之一 tar … note:: 十年磨一剑&#xff0c;霜刃未曾试。 贾岛《剑客 / 述剑》 Linux的tar命令可以用来压缩或者解压缩文件。 官方定义为&#xff1a; tar - an archiving utility 语法 …

7.27 SpringBoot项目实战 之 整合Swagger

文章目录 前言一、Maven依赖二、编写Swagger配置类三、编写接口配置3.1 控制器Controller 配置描述3.2 接口API 配置描述3.3 参数配置描述3.4 忽略API四、全局参数配置五、启用增强功能六、调试前言 在我们实现了那么多API以后,进入前后端联调阶段,需要给前端同学提供接口文…

花七天时间整理了3.5W字的全栈自动化测试面试题(答案+学习路线)!(适合各级软件测试人员)

在面试战场上&#xff0c;我们需要像忍者一样灵活&#xff0c;像侦探一样聪明&#xff0c;还要像无敌铁金刚一样坚定。只有掌握了这些技巧&#xff0c;我们才能在面试的舞台上闪耀光芒&#xff0c;成为那个令HR们心动的测试人 前言&#xff1a; 我相信大多测试开发的或多或少经…

微服务概述之单体架构

微服务概述 互联网始于 1969年美国的阿帕网&#xff08;ARPA&#xff09;&#xff0c;最开始的阿帕网只在美国军方使用。随着时间的推移&#xff0c;一些大学也开始加入建设&#xff0c;慢慢演化成了现在的因特网 &#xff08;Internet&#xff09;。随着计算机网络的普及&…

猫头虎分享已解决Bug || Error: ImagePullBackOff (K8s)

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通Golang》…

FPGA——时序分析与约束

FPGA时序分析与约束 FPGA结构基础数据传输模型Quartus II 时序报告Quartus II 中TimeQuest的操作实操 时序分析&#xff1a;通过分析FPGA内部各个存储器之间的数据和时钟传输路径&#xff0c;来分析数据延迟和时钟延迟的关系&#xff0c;保证所有寄存器都可以正确寄存数据。 数…

云卷云舒:【实战篇】云主机/虚拟机迁移

1. 简介 用户原有业务通过不同版本型号、不同操作系统的主机承载&#xff0c;形式上包括物理服务器、虚拟机、公有云主机等。随着业务不断扩张&#xff0c;需要将其业务云化转型&#xff0c;必须保证上云过程数据完整&#xff0c;业务平滑过度。 如果将所有业务系统都重新部署…

JS加密/解密之常见的JS代码加密

JS混淆加密是一种将JS代码转换为更难理解和阅读的格式的技术&#xff0c;目的是为了保护JS代码的版权和安全&#xff0c;防止被恶意修改或盗用。JS混淆加密通常包括以下几种方法&#xff1a; 变量重命名&#xff1a;将变量名替换为随机的字母或符号&#xff0c;使得代码的逻辑…

vue3 修饰符大全(近万字长文)

系列文章目录 TypeScript 从入门到进阶专栏 文章目录 系列文章目录前言一、事件修饰符&#xff08;Event Modifiers&#xff09;1、.stop&#xff08;阻止事件冒泡&#xff09;2、.prevent&#xff08;阻止事件的默认行为&#xff09;3、.capture&#xff08;使用事件捕获模式…

6.4 通过IO实现文件的读取与写入

6.4 通过IO实现文件的读取与写入 1. File类及常用方法2. 通过字节字符流实现文件读取与写入1. 流2. 字节输入输出流 InputStream与OutputStream3. 字符输入输出流实现文本读取与写入4. 字节流与字符流的相互转化 3. 缓冲区及应用4. 1. File类及常用方法 package com.imooc.io;i…

1、C语言:数据类型/运算符与表达式

数据类型/运算符/表达式 1.数据类型与长度2.常量3.声明4. 运算符5. 表达式 1.数据类型与长度 基本数据类型 类型说明char字符型&#xff0c;占用一个字节&#xff0c;可以存放本地字符集中的一个字符int整型&#xff0c;通常反映了所有机器中整数的最自然长度float单精度浮点…

独占锁ReentrantLock的原理

类图结构 ReentrantLock是可重入的独占锁&#xff0c;同时只能有一个线程可以获取该锁&#xff0c;其他获取该锁的线程会被阻塞而被放入该锁的AQS阻塞队列里面。 首先看下ReentrantLock的类图以便对它的实现有个大致了解。 从类图可以看到&#xff0c;ReentrantLock最终还是使…

如何使用静态IP代理解决Facebook多账号注册并进行网络推广业务?

在当今的数字时代&#xff0c;社交媒体成为了企业进行网络推广的一个重要途径&#xff0c;其中&#xff0c;Facebook是最受欢迎的社交媒体之一&#xff0c;因为它可以让企业通过创建广告和页面来推广他们的产品或服务。 但是&#xff0c;使用Facebook进行网络推广时&#xff0…