苍穹外卖-day03

1.公共字段自动填充

需求分析和设计

❗:多个业务表中出现多个相同字段,这样会使程序中出现大量冗余代码,在日后如果需要修改表结构,则很不便利,需要逐一修改

image-20250309225452726

这些字段只有在 insert,update操作时会去修改,所以只需要通过注解拦截这isnert,update操作,然后给他们自动填充

:解决办法:使用AOP给指定方法进行拦截,自动修改公共字段,

:实现思路:

  • 自定义注解AutoFill注解, 用于标识需要进行公共字段自动填充的方法
  • 自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射为公共字段赋值
  • Mapper的方法上加入AutoFill

image-20250309230531155

代码实现

TODO

为什么使用前置通知,

定义注解类

  • annotation/AutoFill.java
//该注解只能出现在方法上
@Target(ElementType.METHOD)
//表示注解在运行时保留,可以通过反射读取。
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //使用枚举,指定value的值的范围
    OperationType value();

}

定义切面类

  • aspect/AutoAspect.java
@Aspect//定义切面类
@Component//纳入Bean管理
@Slf4j//记录日志
public class AutoAspect {

    /**
     * 切入点
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill))")
    public void autoFillPointCut() {
    }

    /**
     * 前置通知,为新增和修改操作的公共字段自动填充
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint) {
        log.info("开始进行公共字段的自动填充");

        //获取当前被拦截的方法上的数据库操作类型
        OperationType operationType = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(AutoFill.class).value();

        //获取方法的参数
        Object[] args = joinPoint.getArgs();
        if (args == null || args.length == 0) {
            log.error("方法参数为空,无法进行自动填充");
            return;
        }

        //获取当前被拦截的方法的参数--实体对象  ,,这里获取的是方法上的第一个属性对象
        Object entity = args[0];

        //准备赋值的数据
        Long currentId = BaseContext.getCurrentId();
        LocalDateTime currentTime = LocalDateTime.now();

        //反射获取当前类是否具备设置公共字段的方法
        if (operationType == OperationType.INSERT) {
            try {
                //AutoFillConstant.SET_CREATE_TIME 是用提前创建好的常量避免出错
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                //通过反射设置公共字段
                setCreateTime.invoke(entity, currentTime);
                setUpdateTime.invoke(entity, currentTime);
                setCreateUser.invoke(entity, currentId);
                setUpdateUser.invoke(entity, currentId);
            } catch (Exception e) {
                log.error("反射设置公共字段失败:{}", e.getMessage());
            }

        } 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, currentTime);
                setUpdateUser.invoke(entity, currentId);
            } catch (Exception e) {
                log.error("反射设置公共字段失败:{}", e.getMessage());
            }
        }
        //根据当前不同的操作类,为对应的属性通过反射来赋值
    }
}

Mapper层的,INSTER,UPDATE,操作加上AutoFill注解

    /**
     * 插入数据
     * @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);

//... ... 给所有相关方法加上注解

最后删除service层进行填充的冗余代码,还有使用builder构建的对象填充的冗余字段删除 。

功能测试 & 提交代码

测试成功!!!

image-20250310185040170


2.新增菜品

需要分析和设计

接口设计 :

  • 根据类型查询分类
  • 文件上传
  • 新增菜品

image-20250310192040512

数据库设计:

  • dish(菜品表)
  • dish_flavor(菜品口味表)

逻辑外键:即不是数据库中真正的外键,而是靠java程序维护的外键

image-20250310192254726

代码开发

1.文件上传
1.1接口设计

开发文件上传接口:

关于阿里云oss创建参照黑马程序员2023新版JavaWeb视频p148

什么是OSS:

即:(Object Storage Service)一个阿里云对象存储服务,用户可以通过网络随时存储和调用包括文本,图片,音频,视频等各种文件

  • 完成注册
  • 配置yaml文件

在aplication-dev设置oss属性,用于个人开发者,便于后期切换生产环境,并在aplication配置中进行变量引用,

oss服务器地址(endpoint),我也找不到在根据所选地域直接找,我选的是华东1(杭州)

https://help.aliyun.com/zh/oss/user-guide/regions-and-endpoints 进入连接查找

oss服务器地址:oss-cn-hangzhou.aliyuncs.com

❗:切记使用外网Endpoint,开始因为使用了内网Endpoiont,导致图片上传不到OSS

image-20250310231037661

  • 配置文件
#application.yml
sky: 
  alioss:
    endpoint: ${sky.alioss.endpoint}
    bucket-name: ${sky.alioss.bucket-name}
    access-key-id: ${sky.alioss.access-key-id}
    access-key-secret: ${sky.alioss.access-key-secret}

#aplication-dev.yml
 sky: 
   alioss:
    bucket-name: [你自己的oss服务器名字]
    endpoint: [你自己的oss服务器地址]
    access-key-id: [AccessKey ID]
    access-key-secret: [AccessKey Secret]

❗ yml配置oss endpoint的时候记得把前面的https://去掉。

此外:初始工程提供了com.sky.properties.AliOssProperties这一工具类, 用于将文件上传到 OSS,并返回文件的访问 URL。

  • config/OssConfiguration 在server模块配置类部分进行注入配置

@Bean注解用于标注一个方法,表示该方法将返回一个由 Spring 管理的 bean 实例。详细介绍

1.2代码实现
/**
 * 阿里云OSS配置类
 * @author Han
 * @since
 */
@Component
@Slf4j
public class OssConfiguration {
    public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){
        log.info("开始初始化阿里云OSS配置...");
        return new AliOssUtil(aliOssProperties.getEndpoint(),aliOssProperties.getAccessKeyId(),aliOssProperties.getAccessKeySecret(),aliOssProperties.getBucketName());
    }
}

  • CommonController 文件上传
@RestController
@RequestMapping("/admin/common")
@Slf4j
//Controller
public class CommonController {
    @Autowired
    private AliOssUtil aliOssUtil;
    /**
     * 文件上传
     * @return
     */
    @PostMapping("/upload")
    @ApiOperation("文件上传")
    public Result<String> upload(MultipartFile file){
        log.info("文件上传:{}", file.getOriginalFilename());
        try{
            //获取原始文件名后缀
            String extension = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
            //用uuid作为文件名,防止生成的临时文件重复
            String objectName = UUID.randomUUID().toString() + extension;
            //上传文件
            String filePath = aliOssUtil.upload(file.getBytes(), objectName);
            log.info("图片上传成功:{}",filePath);
            return Result.success(filePath);
        } catch (IOException e) {
            log.error("文件上传失败:{}",e.getMessage());
        }
        return Result.error(MessageConstant.UPLOAD_FAILED);
    }
}

测试成功!

image-20250312183958593
2.新增菜品接口代码开发
2.1接口设计:
image-20250313182811812

使用DishDTO接受前端传给后端的数据

image-20250313182843667

接口设计
接口(/admin) 方法 功能
/category/list(已完成) 根据类型查询分类
/dish/list get 根据分类id查询菜品
/upload(已完成) 上传图片
/setmeal post 新增套餐
2.2代码开发
  • Controller
@RestController
@RequestMapping("admin/dish")
@Slf4j
@Api(tags = "菜品相关接口")
public class DishController {

    @Autowired
    private DishService dishService;

       @PostMapping
       @ApiOperation("新增菜品")
        public Result save(@RequestBody DishDTO dishDTO){
           dishService.saveWithFlavor(dishDTO);
           return Result.success();
        }
}
  • service层
//菜品接口	
public interface DishService {

    /**
     * 新增菜品和对应口味
     * @param dishDTO
     */
    public void saveWithFlavor(DishDTO dishDTO);
}

//菜品实现类
	@Service
public class DishServiceImpl implements DishService {

    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private DishFlavorMapper dishFlavorMapper;

    /**
     * 新增菜品和对应口味
     * @param dishDTO
     */
    @Override
    public void saveWithFlavor(DishDTO dishDTO) {
        Dish dish = new Dish();
        //使用对象拷贝
        BeanUtils.copyProperties(dishDTO,dish);

        //掉用mapper,插入一个新菜品
        //之前完成了公共字段填充,现在只需要在mapper层上加上注解即可
        dishMapper.insert(dish);

        //获取菜品id,上面调用的mapper动态sql已经指定了将自增主键返回到实体类的id属性
        Long dishId = dish.getId();

        /*在新增菜品的时候,还要连同口味一起新增,口味使用集合存储,使用动态sql批量进行插入*/
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if (flavors != null && flavors.size() > 0 ) {
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dishId);
            });
        }
        dishFlavorMapper.insertBatch(flavors);

    }
}

❕:(原子性)这里有两个数据库操作同时进行,所以要开始事务,以便数据统一

如果数据库表dish已经插入数据库,而表dish_flavor 没有插入数据库那么会造成数据不完整,不一致的问题

关于事务详情:https://blog.csdn.net/MinggeQingchun/article/details/119579941

image-20250313210530357

  • mapper层
<!--DishMapper.xml-->
    
    
<!--useGeneratedKeys="true":让 MyBatis 在插入数据后自动获取数据库生成的主键(如 自增id)。-->
<!--  keyProperty="id" 主要用于获取数据库自动生成的主键值,并将其回填到 Java 对象的 id 属性中。-->
   
    
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
    insert into dish (name, category_id, price, image, description, status, create_time, update_time, create_user, update_user)
    values
    (#{name},#{categoryId},#{price},#{image},#{description},#{status},#{createTime},#{updateTime},#{createUser},#{updateUser})
    </insert>

    
    
<!--DishFlavorMapper.xml-->
    
    
        <insert id="insertBatch">
        insert into dish_flavor (dish_id, name, value) values
        <foreach collection="flavors" item="df" index="index" separator=",">
            (#{df.dishId},#{df.name},#{df.value})
        </foreach>

    </insert>

出现的问题mybatis中

传入字段顺序和数据库中字段顺序不一样,导致错误

image-20250313211145568

功能测试&提交

测试成功,提交代码

image-20250313211731231


3.菜品分页查询

需求分析和设计

image-20250314120746914

image-20250314120804022

  • 接口设计

image-20250314120925869

设计一个DTO用于接受前端传给后端的数据

image-20250314121003518

  • vo类设计

categoryName,来自另一个表

因为查询到的数据来自两张表, 所以需要一个VO类进行统一封装然后返回给前端

将两个表中数据封装到一个VO对象中

image-20250314224449241

代码开发

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

           return Result.success(pageResult);
        }
  • service
//接口
  PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO);

//impl
   /**
     *  菜品分页查询
     * @param dishPageQueryDTO
     * @return
     */
    @Override
    public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
        log.info("使用分页插件");
        PageHelper.startPage(dishPageQueryDTO.getPage(),dishPageQueryDTO.getPageSize());
        Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);
        return new PageResult(page.getTotal(),page.getResult());
    }
  • mapper
	/**
     * 菜品分页查询
     * @param dishPageQueryDTO
     * @return
     */
    Page<DishVO> pageQuery(DishPageQueryDTO dishPageQueryDTO);
  • DishMapper.xml
   <!--动态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 name!=''">
                and d.name like concat('%',#{name},'%')
            </if>
            <if test="categoryId!=null and categoryId!=''">
                and d.category_id=#{categoryId}
            </if>
            <if test="status!=null and status!=''">
                and d.status=#{status}
            </if>
        </where>
        order by d.create_time desc
    </select>

内连接,外链接,多表查询... ...参考https://developer.aliyun.com/article/1397957

功能测试&提交

前后端联调,测试成功

image-20250314223142827

项目采用Springboot2.X.x不支持JDK21,由于当天完成学校作业把idea项目jdk设置为了21,导致项目不能正常运行

Caused by: org.springframework.core.NestedIOException: ASM ClassReader failed to parse class file - probably due to a new Java class file version that isn't supported yet: file [D:\Code\SkyTakeaway\后端初始工程\sky-take-out\sky-server\target\classes\com\sky\SkyApplication.class]; nested exception is java.lang.IllegalArgumentException: Unsupported class file major version 65


4.删除菜品

需求分析和设计

需求分析:

  • 可以单独删除,
  • 也可以批量删除
  • 启售中菜品不能删除
  • 被套餐关联的菜品不能删除
  • 删除菜品后,关联的口味数据也要删除

image-20250314230538828

接口设计:

只用一个批量删除的接口,替代单独删除

image-20250314230620795

数据库设计:

image-20250314230709754

代码开发

  • controller
	/**
     * 菜品批量删除
     * @param ids
     * @return
     */
    @DeleteMapping
        @ApiOperation("菜品批量删除")
//    RequestParam注解自动讲前端传的字符串,转换为集合
        public Result delete(@RequestParam List<Long> ids){
            log.info("菜品批量删除:{}",ids);
            dishService.deleteBatch(ids);
            return Result.success();
        }
  • service
    /**
     *
     * 批量删除菜品
     * @param ids
     */
    public void deleteBatch(List<Long> ids){
        log.info("开始批量删除ids={}",ids);
        //1.判断当前菜品是否启售中,如果启售中则不能删除
        for (Long id : ids) {
            Dish dish = dishMapper.getById(id);
            if (dish.getStatus() == StatusConstant.ENABLE){
                //状态在启售中不能删除
                throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
            }
        }

        //2.判断当前菜品是非被套餐关联,如果被套餐关联则不能删除
        List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(ids);
        log.info("setmealIds={}",setmealIds);
        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);
        }
    }

TODO:日后优化这里的for循环,

这里的删除菜品表中数据,有待优化,现在如果要删除一个数据就会循环执行两个sql语句,有些占用资源,

------>

可以在mapper优化sql,让所有被删除id全部传入mapper层。使用sql的in语法:

检查某个字段的值是否匹配给定集合中的任意一个值

SELECT column1, column2, ...
FROM table_name
WHERE column_name IN (value1, value2, value3, ...);
  • mapper
 
//DishMapper
//将deleteById函数修改为
    /**
     * 根据id批量删除菜品
     * @param ids
     */
    void deleteByIds(List<Long> ids);

//-----------------------------------------

//DishFlavorMapper
//原来deleteByDishId函数改为
    /**
     * 根据批量菜品id删除口味数据
     * @param dishIds
     */
    void deleteByDishIds(List<Long> dishIds);
  • DishMapper
//DishMapper 动态xml
    <delete id="deleteByIds">
        delete from dish where id in
        <foreach collection="ids" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </delete>

//-----------------------------------------

//DishFlavorMapper 动态xml
    <delete id="deleteByDishIds">
        delete from dish_flavor where dish_id in
        <foreach collection="dishIds" item="dishId" open="(" separator="," close=")">
            #{dishId}
        </foreach>
    </delete>

功能测试&提交

已成功

image-20250315204344245


5.修改菜品

需求分析和设计

  • 原型图
image-20250316165101138
  • 业务分析

1.回显数据,根据id查询数据;回显dish表中数据然后在回显口味表数据

2.修改数据,修改dish表数据,删除口味再,新增口味以达到和修改口味同样的修改操作

3.根据类型查询分类;(已完成)

4.文件上传;(已完成)

  • 接口设计

image-20250316165508164

image-20250316165807101

代码开发

  • 1.回显菜品及口味数据
  • controller
   //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);
    }
  • service
//DisService接口
 	/**
     * 根据id查询菜品及其对应口味数据
     * @param id
     * @return
     */
    DishVO getByIdWithFlavor(Long id);


//impl
 /**
     * 根据id查询菜品及其对应口味数据
     * @param id
     * @return
     */
    @Override
    public DishVO getByIdWithFlavor(Long id) {
        //获取菜品数据
        Dish dish = dishMapper.getById(id);
        //获取菜品口味数据
        List<DishFlavor> flavors = dishFlavorMapper.getByDishId(id);
        //将菜品和口味数据封装到VO对象中
        DishVO dishVO = new DishVO();
        BeanUtils.copyProperties(dish, dishVO);
        dishVO.setFlavors(flavors);
        return dishVO;
    }
  • mapper (之前已经完成,可以直接用)
//DishMapper
  	/**
     * 根据id查询菜品
     * @param id
     * @return
     */
    @Select("select * from dish where id = #{id}")
    Dish getById(Long id);


//DishFlavorMapper
    /**
     * 根据菜品id查询口味数据
     * @param dishiId
     * @return
     */
    @Select("select * from dish_flavor where dish_id = #{dishId}")
    List<DishFlavor> getByDishId(Long dishiId);

这里完成了修改菜品的第一步,回显菜品数据,前后端联调测试一下,成功!!!

image-20250316181137632

下面再完成最后的修改功能

  • controller
    /**
     * 根据id修改菜品
     * @param dishDTO
     * @return
     */
    @PutMapping
    @ApiOperation("根据id修改菜品")
    public Result update(@RequestBody DishDTO dishDTO) {
        log.info("修改菜品:{}", dishDTO);
        dishService.updateWithFlavor(dishDTO);
        return Result.success();
    }
  • service
//DishService接口   

    /**
     * 根据id更新菜品及其对应口味数据
     *
     * @param dishDTO
     */
    public void updateWithFlavor(DishDTO dishDTO);

	

//DishServiceImpl实现

	/**
     * 根据id更新菜品及其对应口味数据
     *
     * @param dishDTO
     */
    @Transactional
    @Override
    public void updateWithFlavor(DishDTO dishDTO) {
        //1.更新菜品基本数据
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);
        dishMapper.update(dish);
        //更新菜品口味数据
        //先删除原来的口味数据
        dishFlavorMapper.deleteByDishId(dishDTO.getId());
        //再插入新的口味数据
        List<DishFlavor> flavors = dishDTO.getFlavors();
        //新增的口味数据要重新设置菜品id
        if(flavors != null && flavors.size() > 0){
            flavors.forEach(flavor -> flavor.setDishId(dishDTO.getId()));
            dishFlavorMapper.insertBatch(flavors);
        }

    }

@Transactional : 这里涉及到两张表数据的修改,为了确保数据的一致性,使用事务

什么情况使用事务:涉及到多张表的(插入,修改,删除)操作时应当使用事务

场景 是否需要事务? 原因
多个表的增删改 ✅ 需要 保证数据一致性
一个业务涉及多步 SQL 操作 ✅ 需要 避免部分成功、部分失败
高并发操作(如库存扣减) ✅ 需要 防止超卖、数据竞争
转账、扣款等资金交易 ✅ 必须 资金安全,防止丢失
级联删除多个关联数据 ✅ 需要 避免部分删除,数据残留
单个表的 SELECT 查询 ❌ 不需要 读操作不影响数据
单个表的简单 INSERT/UPDATE/DELETE ❌ 一般不需要 数据库本身保证原子性
日志记录等非关键操作 ❌ 不需要 失败不会影响业务

事务是什么:即A C I D

  1. A(原子性,Atomicity)
    事务中的所有操作要么全部执行成功,要么全部执行失败。事务是一个原子操作,不可分割。如果事务中的某个操作失败,所有之前的操作都会被回滚,确保数据的一致性。

  2. C(一致性,Consistency)
    事务执行前后,数据库的状态必须是一致的。事务的执行会使数据库从一个一致性状态转换到另一个一致性状态,确保数据库不会在错误状态下运行。

  3. I(隔离性,Isolation)
    不同事务之间的操作相互独立,互不干扰。事务的隔离性保证了一个事务的执行不会被其他事务影响,避免脏读、不可重复读、幻读等问题。不同的隔离级别(如 READ COMMITTED、REPEATABLE READ)提供不同程度的隔离。

  4. D(持久性,Durability)
    一旦事务提交,其结果将永久保存在数据库中。即使发生系统崩溃,已提交的事务所做的修改依然会保留在数据库中,不会丢失。

  • mapper
  //DishMapper
    /**
     * 根据id更新菜品
     *
     * @param dish
     */
	//使用之前的公共字段自动填充,修改时间,修改人
    @AutoFill(OperationType.UPDATE)
    void update(Dish dish);



//DishFlavorMapper
    /**
     * 根据菜品id删除口味
     *
     * @param id
     */
    @Delete("delete from dish_flavor where dish_id = #{id}")
    void deleteByDishId(Long id);


    /**
     * 批量插入口味
     *
     * @param flavors
     */
    void insertBatch(List<DishFlavor> flavors);

⚠️提示:先删除,再插入,代替修改操作,这种方式可以提高性能,同时降低了sql的复杂度,保证数据的一致性

  • mybatis映射文件
<!--DishMapper-->
     <!--根据菜品id 动态修改菜品-->
    <update id="update">
        update dish
        <set>
            <if test="name!=null and name!=''">
                name=#{name},
            </if>
            <if test="categoryId!=null">
                category_id=#{categoryId},
            </if>
            <if test="price!=null">
                price=#{price},
            </if>
            <if test="image!=null and image!=''">
                image=#{image},
            </if>
            <if test="description!=null and description!=''">
                description=#{description},
            </if>
            <if test="updateTime!=null">
                update_time=#{updateTime},
            </if>
            <if test="updateUser!=null and updateUser!=''">
                update_user=#{updateUser},
            </if>
            <if test="status!=null">
                status=#{status},
            </if>
        </set>
        where id=#{id}
    </update>


<!--DishFlavorMapper-->
<!--批量新增口味-->
    <insert id="insertBatch">
        insert into dish_flavor (dish_id, name, value) values
        <foreach collection="flavors" item="df" index="index" separator=",">
            (#{df.dishId},#{df.name},#{df.value})
        </foreach>
    </insert>

❗对于数据库update和delete操作一定要谨慎,一定要多检查where子句等细节,后续一定要学习数据库备份等相关安全操作,关于update和delete误操作的恢复办法

功能测试&提交

测试成功 、提交

image-20250316190702811


这里应为其他原因使用ljdk21,忘记切换回springboot2.X.X项目对应的jdk17导致不能正常运行

TODO:为了避免出错先设置为jdk17,日后再升级springBoot3

springboot2 ——>springboot3 迁移指南https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide

image-20250314223413423

posted @ 2025-03-16 19:11  han390  阅读(164)  评论(0)    收藏  举报