苍穹外卖 -day03- 菜品与套餐删除模块笔记

0 阅读9分钟

苍穹外卖 -day03- 菜品与套餐删除模块笔记

  • 本文适合正在做 Java 实战项目、需要掌握业务校验逻辑的开发者,能帮你快速理解多表关联删除的实现思路

【作者说:在这个模块中,我又学习了一些注解知识,并且对一个删除操作的判断校验开发过程有了一定了解,也拓展了一下xml中动态SQL的常用标签,最后是对数据库的表对映关系也有了了解,我会继续学习接下来的相关内容!!!】

一、常用注解汇总

@RequestParam:接收前端上传的参数、字符串、集合,实现参数绑定。 @DeleteMapping:处理删除类型的请求,由接口功能确定请求方式。 @Select / @Delete:MyBatis 注解,可直接在 Mapper 接口书写简单 SQL。 @Transactional:事务注解,保证多表操作同时成功或同时失败,常用于删除套餐 + 关联表数据。

二、删除操作两大核心校验

  • 核心校验规则

删除操作的两类核心校验规则:前者是避免业务数据异常,后者是防止数据库出现脏引用,刚好覆盖了业务合理性和数据完整性两个维度,这两个判断是 “删除安全校验” 的核心.

前者是业务层面的 “在售保护”,避免误删正在售卖的菜品影响订单;后者是数据层面的 “引用保护”,防止删除被套餐关联的菜品导致数据不一致。这种组合校验能从根源上保证数据库的完整性和业务的合理性,很多生产环境的删除接口都会做这两层校验。

  • 在售状态校验开发逻辑
  1. 前置条件(为什么要校验?)

菜品批量删除与在售校验功能:源代码中属于暴力删除,可能会删除火爆售卖的在售菜品,所以需要进行正确的校验,批量删除中如果包含在售商品就抛出异常

  1. 在dishmapper接口里定义:
Integer countSellingDishes(@Param("ids")List<Long> ids);//定义好查询在售菜品数量的接口
  • 为什么这个接口只有定义,没有写具体的实现方法?
  • 这是因为 MyBatis 框架使用了动态代理技术帮我们自动生成了接口的实现类。我们在接口里定义方法签名,再在对应的 XML 映射文件中通过同名的 SQL 语句定义具体的数据库操作逻辑,MyBatis 会在程序启动时,根据接口和 XML的匹配关系,动态创建出实现类的对象,所以我们不需要手动编写实现代码----主要就是在后端定义抽象接口,MyBatis会在相应的映射文件里用SQL实现它
  1. 在批量删除中加上判断条件并调用异常进行抛出,加上相应文字说明::
Integer sellingCount = dishMapper.countSellingDishes(ids);
        if(sellingCount>0){//若在售数量 > 0 则抛出异常
            throw new DeletionNotAllowedException("包含在售菜品,无法删除");
        }
/*ids 就是前端页面勾选的所有待删除菜品的 ID 集合,是一个由 Long 类型数字组成的列表,
你勾选的每一个菜品 ID 都会被放进这个列表里传到后端。*/        

4.在dishmapper的映射文件中加上SQL语句:

<select id="countSellingDishes" resultType="int">
        select count(*) from dish
        where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
        and status = 1
    </select>

在映射文件中了解到的标签:foreach循环遍历,collection集合名字,item个体名字,separator分隔符,open开始片段"(",close结束片段")"

<foreach
    collection="集合名"
    item="每一项名称"
    separator=","
    open="("
    close=")"
></foreach>
  • 套餐关联校验开发逻辑:
  1. 前置条件(为什么要校验?)

菜品批量删除与套餐关联校验功能:源代码中直接删除可能会删除已有套餐内的菜品,这样会导致菜品套餐内容出现偏差,所以需要进行正确的校验,批量删除中如果套餐中包含被删除的菜品就抛出异常,这在后面三张表有简单解释

  1. 在 SetmealDishMapper 接口里定义:
Integer countByDishId(@Param("dishId") Long dishId);
  1. 在菜品批量删除中加上判断条件并调用异常进行抛出,加上相应文字说明:
// 菜品批量删除方法中,在售校验之后,添加套餐关联校验
for (Long dishId : ids) {
    Integer count = setmealDishMapper.countByDishId(dishId);
    if(count > 0){//若关联套餐数量 > 0 则抛出异常
        throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
    }
}
/*ids 就是前端页面勾选的所有待删除菜品的 ID 集合,是一个由 Long 类型数字组成的列表,
你勾选的每一个菜品 ID 都会被放进这个列表里传到后端。*/
  1. 在 SetmealDishMapper 的映射文件中加上 SQL 语句:
<select id="countByDishId" resultType="int">
    select count(*) from setmeal_dish
    where dish_id = #{dishId}
</select>

  • 批量校验与逐行校验的区别与关系说明

在本次菜品删除功能的两套校验逻辑(在售状态校验、套餐关联校验)中, 本笔记统一采用了批量校验的写法,而学习视频中采用的是逐行校验写法,二者思路不同,但最终业务效果完全一致。

  1. 核心区别
  • 逐行校验(黑马视频) 循环遍历每一个菜品 ID,对每个 ID 单独执行一次 SQL 查询,查到违规数据立即抛出异常

优点:逻辑直观、容易理解,适合教学 缺点:批量删除时数据库请求次数多,性能一般

  • 批量校验(笔记写法) 将所有菜品 ID 一次性传入 SQL,只执行 一次查询,用 count(*) 统计违规总数,只要总数 > 0 就直接抛出异常

优点:性能更高、数据库交互更少,企业项目常用 缺点:不能精确定位到具体哪一条违规(业务上不需要)


三、删除与校验的两大比较

  1. 单个删除与批量删除:

单个删除:基础删除单条数据。 批量删除:循环调用单个删除,内部包含校验逻辑。 关系:单个删除是批量删除的子集,批量删除统一做校验和事务控制。

  1. 批量校验与逐行校验对比

批量校验:一条 SQL 统计在售数量,数据库交互少、性能高,但无法精确定位不可删数据。 逐行校验:循环逐个查询判断,提示更精准,但频繁查询数据库,性能较低。

  • 批量校验的优势是减少了数据库交互次数,性能比逐行校验高很多,尤其适合批量处理大量数据的场景,缺点是无法直接定位到具体是哪些菜品不能删;
  • 逐行校验的优点是可以在循环中记录下每个不能删除的菜品 ID 或名称,给用户更精准的提示,但频繁的数据库查询会让批量操作的速度变慢。

四、三张表关系与前端勾选逻辑

  1. 三张表结构

dish:菜品表,存储菜品信息 setmeal:套餐表,存储套餐信息 setmeal_dish:关联表,维护菜品与套餐的多对多关系

  • 规范:一张表对应一个 Mapper 接口 + 一个 XML 映射文件,Service 层处理业务逻辑。
  • 原因:这是分层架构的规范。每个表对应一个 Mapper 接口和 XML 文件,负责该表的数据库操作;Service 层再调用 Mapper 层,处理业务逻辑。虽然看起来文件多,但分工清晰,后期维护和扩展会很方便。比如要改套餐的查询逻辑,直接找 SetmealMapper 相关的文件就行。
  1. 前端勾选框逻辑
  • 前端勾选框需要后端配合两个关键方法。一个是 “获取勾选菜品 ID 列表” 的方法,前端把选中的菜品 ID 传给后端;另一个是 “批量处理关联关系” 的方法,后端用这些 ID 在 SetmealDish 表中批量插入套餐和菜品的关联数据。
  • deleteByIDs写批量删除集合以及相应的映射文件:
<!-- 批量删除菜品:deleteByIds 映射SQL -->
<delete id="deleteByIds">
    delete from dish
    where id in
    <foreach collection="ids" item="id" separator="," open="(" close=")">
        #{id}
    </foreach>
</delete>

<!-- 批量删除套餐关联数据:deleteBySetmealId 映射SQL -->
<delete id="deleteBySetmealId">
    delete from setmeal_dish
    where setmeal_id = #{setmealId}
</delete>
//说明:deleteByIds 对应菜品批量删除,deleteBySetmealId 对应套餐删除时,同步删除关联表数据,和你 Service 层代码完全对应,直接复用即可

五、关键注意点

  1. 注解使用注意:@Param("ids") 必须加,否则 MyBatis 无法识别前端传递的 List 集合(尤其批量操作时),会报 “参数绑定异常”。
  2. SQL 与接口匹配注意:Mapper 接口方法名(如 countSellingDishes)必须和 XML 中标签的 id 完全一致,否则 MyBatis 动态代理无法匹配,报 “找不到方法实现” 异常。
  3. 事务使用注意:涉及多表操作(如删除套餐 + 关联表),必须在 Service 方法上添加 @Transactional,否则可能出现 “删了套餐,没删关联数据” 的脏数据问题。
  4. 关于校验内容的梳理:首先这个删除操作里有两大校验,分别是在售状态校验和套餐关联校验,另外这两大校验又都各有两种方式分别为批量校验和逐行检验,这个是我在做笔记过程中容易混淆的地方,需要读者特别注意一下!!!

六、笔记总结

本文围绕苍穹外卖项目的核心功能展开,重点涵盖常用注解(@RequestParam、@Transactional 等)、删除操作核心逻辑及数据库相关操作。通过在售校验、关联校验保障数据安全与业务正常,借助 MyBatis 动态代理实现接口功能,无需手动编写实现代码。同时明确单个删除与批量删除的关系,补充完整的 SQL、XML 配置及相关代码。此外,梳理了菜品与套餐的多表关系,明确前端勾选框与后端接口的联动逻辑,解决了数据一致性、操作规范性等问题,既满足项目实战需求,也为后续实习、面试提供了完整的知识支撑,整体逻辑清晰、可直接应用于实际开发。