文章目录
新建套餐信息
在外卖管理系统中,新建套餐是核心功能之一。新建套餐不仅需要保存套餐的基本信息(如名称、所属分类、价格、图片等),还必须同步保存"套餐与菜品的关联关系"(即该套餐包含哪些菜品、每种菜品的数量)。下方图片展示的是新建套餐的前端操作界面,界面中清晰划分了"套餐基本信息填写区"和"菜品选择区",用户完成信息录入后提交,后端将通过以下步骤处理请求。

(一)SetmealMapper接口添加方法
Mapper接口是数据访问层的入口,新建套餐首先需要定义"插入套餐基本信息"的方法。这里通过注解和自定义注解结合,简化开发并保证公共字段的自动填充。

java
/**
* 新增套餐(仅插入套餐基本信息,不包含关联菜品)
* @param setmeal 封装了套餐基本信息的实体类(如名称、分类ID、价格等)
*/
@AutoFill(OperationType.INSERT)
void insert(Setmeal setmeal);
- @AutoFill(OperationType.INSERT) :这是自定义的"自动填充注解",作用是在执行插入操作时,自动为
Setmeal
实体的公共字段(如createTime
创建时间、updateTime
修改时间、createUser
创建人、updateUser
修改人)赋值,无需手动编写代码,减少重复工作。 - 方法作用 :仅负责将套餐的基本信息插入到
setmeal
表中,关联菜品的插入会由其他方法处理。
(二)SetmealMapper.xml文件添加sql语句
Mapper接口的insert
方法需要通过XML编写具体的SQL,同时配置"获取自动生成的主键"------因为后续关联菜品时,需要用这个主键(套餐ID)绑定关系。

xml
<!-- 新增套餐信息(插入套餐基本数据到setmeal表) -->
<insert id="insert" parameterType="Setmeal" useGeneratedKeys="true" keyProperty="id">
insert into setmeal
(category_id, name, price, status, description, image, create_time, update_time, create_user, update_user)
values (#{categoryId}, #{name}, #{price}, #{status}, #{description}, #{image}, #{createTime}, #{updateTime},
#{createUser}, #{updateUser})
</insert>
- 关键配置解析 :
useGeneratedKeys="true"
:开启"获取数据库自动生成的主键"功能(适用于主键自增的表,如setmeal
表的id
字段)。keyProperty="id"
:将数据库生成的主键值,赋值给Setmeal
实体的id
属性------后续通过setmeal.getId()
就能拿到新建套餐的ID,用于关联菜品。- SQL字段说明:
category_id
对应套餐所属分类ID,status
表示套餐状态(0下架/1起售),image
是套餐封面图的路径,其余字段为自动填充的公共字段。
(三)SetmealService业务层接口添加方法
Service层负责封装业务逻辑,新建套餐的核心业务是"同时保存套餐基本信息和套餐-菜品关联关系",因此定义saveWithDish
方法("WithDish"强调包含菜品关联)。

java
/**
* 新增套餐,同时保存套餐和菜品的关联关系
* @param setmealDTO 封装了"套餐基本信息+关联菜品列表"的DTO(数据传输对象)
*/
void saveWithDish(SetmealDTO setmealDTO);
- 为什么用SetmealDTO而非Setmeal ?
Setmeal
实体仅对应setmeal
表的字段,而前端提交的请求中,除了套餐基本信息,还包含setmealDishes
(关联的菜品列表,如"鱼香肉丝x2份""米饭x1份")。SetmealDTO
在Setmeal
基础上新增了setmealDishes
属性,刚好能接收前端的完整数据,避免用多个参数传递。
(四)SetmealServiceImpl接口实现类重写方法
Service实现类是业务逻辑的核心执行层,这里需要通过事务控制保证"套餐插入"和"关联菜品插入"同时成功或失败,避免数据不一致(比如套餐插成功了,但关联菜品没插,导致套餐是空的)。

java
/**
* 新增套餐,同时保存套餐和菜品的关联关系
* @param setmealDTO 前端传递的完整套餐数据(基本信息+关联菜品)
*/
@Transactional // 事务注解:确保以下操作要么全成功,要么全回滚(比如关联菜品插入失败,套餐也会回滚删除)
public void saveWithDish(SetmealDTO setmealDTO) {
// 1. 将DTO转成Setmeal实体(用于插入套餐基本信息)
Setmeal setmeal = new Setmeal();
// 2. 设置公共字段(这里手动设置,也可通过@AutoFill注解自动填充,视项目配置而定)
setmeal.setCreateTime(LocalDateTime.now()); // 创建时间设为当前时间
setmeal.setUpdateTime(LocalDateTime.now()); // 修改时间设为当前时间
setmeal.setCreateUser(BaseContext.getCurrentId()); // 创建人:从上下文拿当前登录用户ID
setmeal.setUpdateUser(BaseContext.getCurrentId()); // 修改人:同创建人
// 3. 插入套餐基本信息到setmeal表(调用Mapper方法)
setmealMapper.insert(setmeal);
// 4. 获取新建套餐的ID(由Mapper的XML配置自动赋值到setmeal的id属性)
Long setmealId = setmeal.getId();
// 5. 处理关联菜品列表:给每个菜品绑定套餐ID(否则关联表不知道菜品属于哪个套餐)
List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
setmealDishes.forEach(setmealDish -> { // 遍历所有关联菜品
setmealDish.setSetmealId(setmealId); // 给当前菜品设置套餐ID
});
// 6. 批量插入关联数据到setmeal_dish表(批量插入比循环单条插入效率更高)
setmealDishMapper.insertBatch(setmealDishes);
}
- 核心步骤拆解 :
- DTO转实体 :因为
setmealMapper.insert
需要Setmeal
类型参数,所以先创建Setmeal
对象,用于存储基本信息。 - 公共字段赋值 :
BaseContext.getCurrentId()
是从ThreadLocal中获取当前登录用户的ID(比如管理员ID),确保操作人信息准确。 - 获取套餐ID :插入套餐后,通过
setmeal.getId()
拿到数据库自动生成的ID,这是关联菜品的关键。 - 绑定关联关系 :遍历前端传的
setmealDishes
,给每个菜品设置setmealId
,相当于给"菜品"贴了"所属套餐"的标签。 - 批量插入关联数据 :用
insertBatch
批量插入关联表,减少数据库交互次数,提升性能。
- DTO转实体 :因为
(五)SetmealController控制层类添加方法
Controller层是前端请求的入口,负责接收前端提交的套餐数据、调用Service层执行业务、清理缓存(保证数据最新),并返回结果给前端。

java
/**
* 新增套餐(接收前端请求并响应)
* @param setmealDTO 前端以JSON格式提交的"套餐基本信息+关联菜品"数据
* @return 统一响应结果(告知前端新增成功)
*/
@PostMapping // 用POST请求处理"新增"操作(符合HTTP语义,POST用于提交数据)
@ApiOperation("新增套餐") // Swagger注解:生成接口文档时说明接口功能
@CacheEvict(cacheNames = "setmealCache",key = "#setmealDTO.categoryId") // 清理缓存:避免前端拿到旧的套餐列表
public Result save(@RequestBody SetmealDTO setmealDTO) {
// 调用Service层的核心业务方法,执行新增逻辑
setmealService.saveWithDish(setmealDTO);
// 返回成功响应:前端收到后会提示"新增成功",并刷新套餐列表
return Result.success();
}
- 关键注解说明 :
@RequestBody SetmealDTO setmealDTO
:将前端提交的JSON数据,自动转化为SetmealDTO
对象,无需手动解析JSON。@CacheEvict
:清理缓存的注解。如果系统缓存了"某个分类下的套餐列表"(比如cacheNames="setmealCache"
,key
是分类ID),新增套餐后需要清空该分类的缓存,避免前端刷新后仍显示旧列表(比如新增"快餐"分类的套餐后,"快餐"列表要实时更新)。Result.success()
:统一响应格式,前端收到后会触发"新增成功"的提示(如弹窗提示),并重新加载套餐列表,展示新建的套餐。
通过以上步骤,前端提交新建套餐的请求后,后端会完整完成"套餐基本信息插入"和"套餐-菜品关联插入",同时保证数据一致性和缓存时效性,最终实现新建套餐的功能。