尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
餐饮系统套餐管理模块开发实战与数据库设计
1. 项目概述在餐饮管理系统的开发过程中套餐管理模块是核心业务功能之一。这个模块需要实现套餐的完整生命周期管理包括新增、查询、修改、删除以及状态控制等功能。作为开发人员我在实现苍穹外卖系统的套餐管理模块时遇到了不少典型的技术问题和业务逻辑挑战本文将详细记录这些实战经验。套餐管理不同于单菜品管理它涉及到多表关联操作和复杂的事务处理。一个完整的套餐通常包含基础信息名称、价格、分类等包含的菜品组合销售状态控制操作日志记录这些业务特性使得套餐管理模块的开发需要考虑更多细节特别是在数据库设计和事务控制方面。下面我将从数据库设计开始逐步拆解各个功能的实现过程。2. 数据库设计与表关系2.1 核心表结构设计套餐管理涉及两个核心表套餐主表(setmeal)和套餐菜品关系表(setmeal_dish)。这种设计遵循了数据库设计的范式原则同时考虑了查询性能。setmeal表结构CREATE TABLE setmeal ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, name varchar(32) COLLATE utf8mb4_bin NOT NULL COMMENT 套餐名称, category_id bigint NOT NULL COMMENT 分类id, price decimal(10,2) NOT NULL COMMENT 套餐价格, image varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 图片路径, description varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 套餐描述, status int DEFAULT 1 COMMENT 售卖状态 1起售 0停售, create_time datetime NOT NULL COMMENT 创建时间, update_time datetime NOT NULL COMMENT 最后修改时间, create_user bigint NOT NULL COMMENT 创建人id, update_user bigint NOT NULL COMMENT 最后修改人id, PRIMARY KEY (id), UNIQUE KEY idx_name (name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_bin;setmeal_dish表结构CREATE TABLE setmeal_dish ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, setmeal_id bigint NOT NULL COMMENT 套餐id, dish_id bigint NOT NULL COMMENT 菜品id, name varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 菜品名称冗余字段, price decimal(10,2) DEFAULT NULL COMMENT 菜品单价冗余字段, copies int DEFAULT NULL COMMENT 菜品份数, PRIMARY KEY (id), KEY idx_setmeal_id (setmeal_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_bin;2.2 表关系解析套餐模块涉及的表关系网络如下分类表(category) ← 一对多 → 套餐表(setmeal) 套餐表(setmeal) ← 一对多 → 套餐菜品关系表(setmeal_dish) 套餐菜品关系表(setmeal_dish) ← 多对一 → 菜品表(dish)这种设计的关键考虑点冗余字段设计在setmeal_dish表中冗余存储了菜品名称和价格避免每次查询都需要联表逻辑外键使用逻辑外键而非物理外键提高性能同时保持数据完整性状态控制通过status字段实现灵活的起售/停售控制提示在实际开发中对于高频查询但很少变更的数据如菜品名称、价格适当使用冗余字段可以显著提升查询性能。3. 套餐分页查询实现3.1 接口设计与DTO定义分页查询需要支持以下筛选条件套餐名称模糊查询分类ID精确查询状态查询分页参数定义查询DTOData public class SetmealPageQueryDTO { private String name; private Long categoryId; private Integer status; private Integer page; private Integer pageSize; }3.2 三层架构实现Controller层RestController RequestMapping(/admin/setmeal) Api(tags 套餐管理接口) Slf4j public class SetmealController { Autowired private SetmealService setmealService; GetMapping(/page) ApiOperation(套餐分页查询) public ResultPageResult page(SetmealPageQueryDTO setmealPageQueryDTO) { log.info(分页查询套餐{}, setmealPageQueryDTO); PageResult pageResult setmealService.pageQuery(setmealPageQueryDTO); return Result.success(pageResult); } }Service层Service Slf4j public class SetmealServiceImpl implements SetmealService { Autowired private SetmealMapper setmealMapper; Override public PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO) { PageHelper.startPage(setmealPageQueryDTO.getPage(), setmealPageQueryDTO.getPageSize()); PageSetmealVO page setmealMapper.pageQuery(setmealPageQueryDTO); return new PageResult(page.getTotal(), page.getResult()); } }Mapper层select idpageQuery resultTypecom.sky.vo.SetmealVO select s.*, c.name as categoryName from setmeal s left join category c on s.category_id c.id where if testname ! null and name ! and s.name like concat(%,#{name},%) /if if testcategoryId ! null and s.category_id #{categoryId} /if if teststatus ! null and s.status #{status} /if /where order by s.create_time desc /select3.3 踩坑记录问题1页面上的最后操作时间不显示原因返回的VO类型错误使用了没有updateTime字段的DTO解决统一使用SetmealVO作为返回类型问题2SQL报错Column status in where clause is ambiguous原因在多表查询时status字段未指定表别名解决所有字段都加上表别名前缀如s.status经验在多表联查时养成给所有字段添加表别名的习惯可以避免字段歧义问题。4. 新增套餐实现4.1 业务流程分析新增套餐的核心流程保存套餐基本信息保存套餐包含的菜品列表记录操作日志这是一个典型的事务性操作需要使用Transactional保证数据一致性。4.2 关键代码实现Controller层PostMapping ApiOperation(新增套餐) public Result save(RequestBody SetmealDTO setmealDTO) { log.info(新增套餐{}, setmealDTO); setmealService.saveWithDishes(setmealDTO); return Result.success(); }Service层Transactional Override public void saveWithDishes(SetmealDTO setmealDTO) { // 1. 保存套餐基本信息 Setmeal setmeal new Setmeal(); BeanUtils.copyProperties(setmealDTO, setmeal); setmealMapper.insert(setmeal); // 2. 获取生成的套餐ID Long setmealId setmeal.getId(); // 3. 保存套餐菜品关系 ListSetmealDish setmealDishes setmealDTO.getSetmealDishes(); if (CollectionUtils.isNotEmpty(setmealDishes)) { setmealDishes.forEach(dish - dish.setSetmealId(setmealId)); setmealMapper.insertBatch(setmealDishes); } }Mapper层insert idinsert useGeneratedKeystrue keyPropertyid insert into setmeal (...) values (...) /insert insert idinsertBatch insert into setmeal_dish (...) values foreach collectionlist itemitem separator, (#{item.setmealId}, #{item.dishId}, ...) /foreach /insert4.3 关键问题解决问题无法获取插入后生成的套餐ID原因MyBatis插入操作未配置返回主键解决在insert语句中添加useGeneratedKeystrue keyPropertyid技巧对于批量插入操作使用MyBatis的foreach标签可以显著提高性能。实测显示批量插入比单条循环插入快5-10倍。5. 删除套餐实现5.1 业务规则删除套餐时需要检查套餐状态起售中的套餐不能删除删除套餐主表记录删除关联的套餐菜品关系记录记录操作日志5.2 代码实现Service层Transactional Override public void deleteBatch(ListLong ids) { // 1. 检查套餐状态 Integer count setmealMapper.countByIdsAndStatus(ids, StatusConstant.ENABLE); if (count 0) { throw new SetmealEnableFailedException(存在起售中的套餐无法删除); } // 2. 删除套餐记录 setmealMapper.deleteByIds(ids); // 3. 删除关联菜品 setmealMapper.deleteBySetmealIds(ids); }Mapper层select idcountByIdsAndStatus resultTypeinteger select count(*) from setmeal where id in foreach collectionids itemid open( separator, close) #{id} /foreach and status #{status} /select delete iddeleteByIds delete from setmeal where id in foreach collectionids itemid separator, open( close) #{id} /foreach /delete delete iddeleteBySetmealIds delete from setmeal_dish where setmeal_id in foreach collectionids itemid separator, open( close) #{id} /foreach /delete5.3 事务控制要点方法添加Transactional注解先查询后删除的顺序不能颠倒批量删除使用IN语句而非循环单条删除异常处理要明确避免事务回滚不生效6. 修改套餐实现6.1 业务逻辑分析修改套餐是最复杂的操作需要考虑查询回显数据更新套餐基本信息处理菜品列表变更可能涉及增删改状态变更的特殊处理6.2 关键代码实现Service层Transactional Override public void updateWithDishes(SetmealDTO setmealDTO) { // 1. 更新套餐基本信息 Setmeal setmeal new Setmeal(); BeanUtils.copyProperties(setmealDTO, setmeal); setmealMapper.update(setmeal); // 2. 删除原有菜品关系 setmealMapper.deleteBySetmealIds(Collections.singletonList(setmealDTO.getId())); // 3. 插入新的菜品关系 ListSetmealDish setmealDishes setmealDTO.getSetmealDishes(); if (CollectionUtils.isNotEmpty(setmealDishes)) { setmealDishes.forEach(dish - dish.setSetmealId(setmealDTO.getId())); setmealMapper.insertBatch(setmealDishes); } }状态修改单独处理PostMapping(/status/{status}) ApiOperation(套餐起售停售) public Result startOrStop(PathVariable Integer status, Long id) { setmealService.startOrStop(status, id); return Result.success(); } // Service实现 Override public void startOrStop(Integer status, Long id) { // 起售时需要检查套餐下是否有菜品 if (status StatusConstant.ENABLE) { Integer count setmealDishMapper.countBySetmealId(id); if (count 0) { throw new SetmealEnableFailedException(套餐中没有菜品无法起售); } } Setmeal setmeal Setmeal.builder() .id(id) .status(status) .updateTime(LocalDateTime.now()) .updateUser(BaseContext.getCurrentId()) .build(); setmealMapper.update(setmeal); }6.3 性能优化建议对于菜品列表变更采用先删后增的策略比逐条比对修改更高效状态变更单独处理避免不必要的字段更新使用Builder模式构建更新对象减少不必要的属性拷贝7. 常见问题与解决方案7.1 事务失效场景场景1非public方法添加Transactional解决确保事务方法都是public的场景2自调用问题解决避免在类内部调用自己的事务方法场景3异常被捕获未抛出解决确保异常能传播到事务切面7.2 MyBatis使用技巧批量操作使用foreach标签返回主键使用useGeneratedKeys复杂查询使用resultMap进行映射动态SQL使用if/choose等标签7.3 联调常见问题字段名不一致导致数据无法绑定时间格式转换问题空集合处理不当导致NPE分页参数传递错误在实际开发中我发现套餐管理模块最关键的三个技术点是事务控制、批量操作效率和异常处理。特别是在处理套餐修改时如何优雅地处理菜品列表的变化需要仔细设计。通过这次项目实践我对Spring事务管理和MyBatis的高级用法有了更深入的理解。
RELATED

相关推荐

深度学习与知识图谱驱动的专业搜索引擎DSearcher解析

深度学习与知识图谱驱动的专业搜索引擎DSearcher解析

1. 项目背景与核心价值DSearcher是哈尔滨工业大学团队研发的一款面向专业领域的智能搜索工具,它不同于通用搜索引擎的广撒网模式,而是通过深度学习与知识图谱技术实现精准的垂直领域信息检索。我在测试这个工具时发现,它特别适合处理学术文献…

📅 2026/11/5 6:16:45
基于灰狼优化的VMD轴承故障诊断方法

基于灰狼优化的VMD轴承故障诊断方法

1. 项目概述轴承作为旋转机械的核心部件,其运行状态直接影响设备寿命与安全性。表面织构技术通过在轴承表面加工微米级凹槽或纹理,能够显著改善润滑性能。然而,织构参数(如分布角度、深度、面积比)的复杂变化会干扰振动…

📅 2026/11/5 6:16:45
ESM蛋白质语言模型:从序列到结构功能的进化尺度建模

ESM蛋白质语言模型:从序列到结构功能的进化尺度建模

1. 项目概述:为什么ESM不是又一个“AI画图”模型,而是生物学家手里的新显微镜?最近在几个高校实验室的组会上,我连续三次听到不同方向的同事提到同一个词:ESM。一位做蛋白质结构预测的博士后直接把ESM-2的嵌入向量当成…

📅 2026/11/5 6:16:45
MORE NEWS

更多资讯

📰

Redis7数据结构解析与性能优化实践

1. Redis7 数据结构体系概览Redis作为内存数据库的标杆产品,其卓越性能的核心密码就藏在精心设计的数据结构体系中。与常见数据库不同,Redis直接基于特定数据结构实现键值存储,这种"数据结构即存储"的设计哲学使其在时间复杂度与空…

📰

Ubuntu 20.04下ROS Noetic接入STM32超声波串口实践

简介:supersonic.tar.gz 面向ROS与嵌入式控制学习者,围绕STM32微控制器、串口通信和HC-SR04超声波传感器搭建一套可运行的测距与指令联动示例。压缩包共58个文件、约1.63MB,以C源文件(cc/cpp)、头文件(h/he…

📰

Python实现高效端口扫描器:原理与优化实践

1. 项目背景与核心价值端口扫描是网络安全领域最基础也最实用的技术手段之一。作为Python开发者,我们经常需要快速验证服务器开放端口、排查网络策略问题或进行简单的安全检测。市面上的专业扫描工具虽然功能强大,但往往存在以下痛点:体积庞大…

📰

gRPC流式通信:原理、优化与实战指南

1. 为什么需要流式通信方案在现代分布式系统中,实时数据传输已成为刚需。传统RPC调用模式在处理持续数据流时存在明显短板:每次请求-响应都需要建立新连接,不仅延迟高,还造成资源浪费。想象一下监控系统每秒上报指标、聊天应用收发…

📰

现代网络测试仪核心技术解析与应用实践

1. 网络测试仪行业现状与痛点分析当前企业网络架构正经历从传统三层架构向扁平化、虚拟化方向的快速演进,SDN/NFV技术的普及使得网络边界日益模糊。在这种背景下,网络测试设备面临着三个维度的挑战:协议复杂度提升:现代数据中心网…

📰

京东云主机三年期购买指南与配置优化

1. 京东云主机三年期购买与配置解析京东云确实提供三年期的云主机购买选项,这种长期租赁方式在云计算领域被称为"预留实例"。与按年或按月付费相比,选择三年期通常能获得30%-50%的价格优惠,特别适合有长期稳定业务需求的企业用户。…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬