苍穹外卖:菜品分页查询与删除功能(保姆级详解)

📅 发布时间:2026/7/7 22:53:44 👁️ 浏览次数:
苍穹外卖:菜品分页查询与删除功能(保姆级详解)
个人主页北极的代码欢迎来访作者简介java后端学习者❄️个人专栏苍穹外卖日记SSM框架深入JavaWeb✨命运的结局尽可永在不屈的挑战却不可须臾或缺前言前面我们完成了新增菜品的业务功能这里我们将继续完成项目实现菜品的分页查询功能和删除菜品的业务功能提供详细的代码讲解和逻辑解释。分页查询分析产品原型┌─────────────────────────────────────────────────────────────┐ │ 菜品管理 菜品列表 [新增菜品] │ ├─────────────────────────────────────────────────────────────┤ │ [菜品名称] [分类选择▼] [状态▼] [查询] [重置] │ ├─────────────────────────────────────────────────────────────┤ │ □ 菜品图片 菜品名称 分类 价格 状态 最后更新时间 操作 │ ├─────────────────────────────────────────────────────────────┤ │ □ [图片] 宫保鸡丁 川菜 ¥38 ● 起售 2024-01-15 [修改] [停售] │ │ □ [图片] 麻婆豆腐 川菜 ¥28 ● 起售 2024-01-15 [修改] [停售] │ │ □ [图片] 北京烤鸭 主打菜 ¥88 ○ 停售 2024-01-14 [修改] [起售] │ │ □ [图片] 西湖醋鱼 浙菜 ¥68 ● 起售 2024-01-13 [修改] [停售] │ ├─────────────────────────────────────────────────────────────┤ │ 1 2 3 4 5 ... 10 │ │ 每页10条 共43条记录 │ └─────────────────────────────────────────────────────────────┘功能区详解A. 顶部操作区标题导航菜品管理 菜品列表面包屑导航新增按钮右上角醒目位置绿色主按钮B. 查询条件区组件类型作用菜品名称输入框模糊搜索分类选择下拉框按菜品分类筛选状态下拉框起售/停售筛选查询按钮按钮触发查询重置按钮按钮清空条件C. 列表展示区表头设计□ 复选框批量操作预留菜品图片缩略图菜品名称分类所属分类价格带¥符号状态带颜色标识最后更新时间操作链接按钮状态标识● 绿色圆点起售○ 灰色圆点停售操作按钮修改文字链接停售/起售状态切换按钮D分页区页码导航每页显示条数默认10条总记录数统计业务规则根据页码展示菜品信息每页展示十条菜品数据分页查询时可以根据需要根据菜品名称菜品分类菜品状态进行查询。逻辑图示┌─────────┐ ┌─────────┐ ┌─────────┐ │ 前端 │ │ 后端 │ │ 数据库 │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ │ 1. 用户输入查询条件 │ │ │────────────────────│ │ │ │ │ │ 2. 点击查询按钮 │ │ │────────────────────│ │ │ │ │ │ 3. 封装请求参数 │ │ │ GET /admin/dish/page│ │ │ ?page1pageSize10 │ │ │ name宫保status1 │ │ │────────────────────│ │ │ │ │ │ │ 4. 接收参数 │ │ │ DishPageQueryDTO │ │ │────────────────────│ │ │ │ │ │ 5. 调用Service │ │ │ pageQuery() │ │ │────────────────────│ │ │ │ │ │ │ 6. 执行SQL │ │ │ SELECT... │ │ │───┘ │ │ 7. 返回PageResult │ │ │ (totalrecords) │ │ │────────────────────│ │ │ │ │ 8. 返回JSON数据 │ │ │ ────────────────────│ │ │ │ │ │ 9. 渲染表格和分页 │ │ │────────────────────│ │代码实现ControllerpageResult是后端封装分页查询结果的统一对象它包含了总记录数(total)和当前页数据列表(records)两个核心信息前端拿到后就能知道总共有多少条数据、当前页显示哪些数据。GetMapping(/page) ApiOperation(菜品分页查询) public ResultPageResult page (DishPageQueryDTO dishPageQueryDTO){ log.info(菜品分页查询#{},dishPageQueryDTO); PageResult pageResult dishService.pageQuery(dishPageQueryDTO); return Result.success(pageResult);ServicePageHelper.startPage作用像一个开关开启分页功能原理将分页参数存入ThreadLocal当前线程的私有存储空间为什么放在这里必须在查询语句之前执行才能拦截即将执行的SQLdishMapper.pageQuery()—— 执行查询作用看似普通查询实则被PageHelper增强原理PageHelper拦截器会自动修改SQL添加LIMIT子句为什么返回PagePageHelper自动将List包装成Page对象包含分页信息new PageResult()—— 结果封装作用从Page对象中提取关键信息封装成统一格式为什么不用Page对象直接返回Page对象包含太多内部信息PageResult是精简版getTotal()总记录数用于前端分页组件getResult()当前页数据列表用于前端表格渲染page.getResult() 返回的就是物理上Page对象内部存储数据列表的那个ArrayList逻辑上当前页需要展示的N条业务数据用途上提供给前端渲染表格的数据源数量上等于pageSize最后一页可能少于pageSize它是连接数据库原始数据和前端展示界面的桥梁是分页查询中最核心的数据载体。/** * 菜品分页查询 * param dishPageQueryDTO * return */ public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) { PageHelper.startPage(dishPageQueryDTO.getPage(),dishPageQueryDTO.getPageSize()); PageDishVO pagedishMapper.pageQuery(dishPageQueryDTO); return new PageResult(page.getTotal(),page.getResult());Mapper为什么不需要手动拼接page和pageSizeAOP思想PageHelper通过拦截器实现关注点分离业务代码不需要关心分页细节自动计算自动计算偏移量(offset)并拼接LIMIT子句自动COUNT自动执行COUNT查询获取总记录数自动包装自动将List包装成Page对象提供分页信息统一处理所有分页查询使用相同的方式代码更简洁PageHelper的魔法本质startPage()将分页参数存入ThreadLocal拦截器拦截SQL执行从ThreadLocal读取参数SQL改造自动添加COUNT和LIMIT结果包装返回包含分页信息的Page对象select idpageQuery resultTypecom.sky.vo.DishVO select d.*,c.name as categoryName from sky_take_out.dish d left outer join sky_take_out.category c on d.category_id c.id where if testname!null and d.name like concat(%,#{name},%) /if if testcategoryId!null and d.category_id#{categoryId} /if if teststatus!null and d.status#{status} /if /where /select因为我们要查询的是菜品分类的名字而不是ID所以要查询两张表关于PageHelper的底层执行// 2.1 你写的SQL原始 SELECT d.*, c.name as categoryName FROM dish d LEFT JOIN category c ON d.category_id c.id WHERE d.name LIKE %鸡% // 2.2 PageHelper拦截后实际执行了2条SQL // 第1条SQL自动执行的COUNT查询 SELECT COUNT(*) FROM ( SELECT d.*, c.name as categoryName FROM dish d LEFT JOIN category c ON d.category_id c.id WHERE d.name LIKE %鸡% ) tmp_count; // 第2条SQL自动添加LIMIT的分页查询 SELECT d.*, c.name as categoryName FROM dish d LEFT JOIN category c ON d.category_id c.id WHERE d.name LIKE %鸡% LIMIT 0, 10; -- PageHelper自动添加的 // 第3步PageHelper把两条结果包装成Page对象返回 // page对象包含了total(43)和list(10条数据)删除菜品需求分析和设计可以一次删除一个菜品也可以一次删除多个菜品起售的菜品不能删除被套餐关联的菜品不能删除删除菜品后关联的菜品口味数据也要删除数据库设计dish表dish-flavor表setmeal-dish表实现流程Controllerpackage com.sky.controller.admin; /** * 菜品管理Controller * 接收前端删除菜品的请求 */ RestController RequestMapping(/admin/dish) Api(tags 菜品管理) Slf4j public class DishController { Autowired private DishService dishService; /** * 删除菜品支持批量 * param ids 菜品ID列表例如ids1,2,3 * return */ DeleteMapping ApiOperation(删除菜品) public Result delete(RequestParam ListLong ids) { log.info(删除菜品ids{}, ids); // 调用Service层执行业务 dishService.deleteBatch(ids); return Result.success(); } }/** * 解析 * 1. DeleteMapping处理HTTP DELETE请求 * 2. RequestParam ListLong ids接收URL参数如 /admin/dish?ids1,2,3 * 3. 直接调用ServiceController不处理业务逻辑 */Servicepackage com.sky.service; public interface DishService { /** * 批量删除菜品 * param ids 菜品ID列表 */ void deleteBatch(ListLong ids); } /** * 解析 * 1. 定义业务接口明确功能 * 2. 批量删除方法接收ID列表 */Service层实现 - 核心业务逻辑package com.sky.service.impl; /** * 菜品管理Service实现类 * 核心业务逻辑都在这里 */ Service Slf4j public class DishServiceImpl implements DishService { Autowired private DishMapper dishMapper; Autowired private DishFlavorMapper dishFlavorMapper; Autowired private SetmealDishMapper setmealDishMapper; /** * 批量删除菜品 * param ids 菜品ID列表 */ Override Transactional // 事务注解保证数据一致性 public void deleteBatch(ListLong ids) { log.info(批量删除菜品ids{}, ids); // 第1步参数校验 if (ids null || ids.isEmpty()) { throw new BusinessException(请选择要删除的菜品); } // 第2步检查菜品状态是否起售 // 根据ID列表查询所有菜品信息 ListDish dishList dishMapper.selectByIds(ids); for (Dish dish : dishList) { if (dish.getStatus() 1) { // 1表示起售 throw new BusinessException(菜品【 dish.getName() 】正在起售中不能删除); } } // 第3步检查是否被套餐关联 // 根据菜品ID查询关联的套餐ID ListLong setmealIds setmealDishMapper.getSetmealIdsByDishIds(ids); if (setmealIds ! null setmealIds.size() 0) { // 查询套餐名称用于提示 ListSetmeal setmeals setmealMapper.selectByIds(setmealIds); String setmealNames setmeals.stream() .map(Setmeal::getName) .collect(Collectors.joining(、)); throw new BusinessException(菜品已被套餐【 setmealNames 】关联不能删除); } // 第4步删除关联的口味数据 dishFlavorMapper.deleteByDishIds(ids); log.info(删除菜品关联的口味数据dishIds{}, ids); // 第5步删除菜品本身 dishMapper.deleteByIds(ids); log.info(删除菜品成功ids{}, ids); } }Mapper层 - 数据访问package com.sky.mapper; Mapper public interface DishMapper { /** * 根据ID列表批量查询菜品 * param ids 菜品ID列表 * return 菜品列表 */ ListDish selectByIds(Param(ids) ListLong ids); /** * 根据ID列表批量删除菜品 * param ids 菜品ID列表 */ void deleteByIds(Param(ids) ListLong ids); }注意事项根据主键 id查询菜品的起售状态主键id返回所有的字段所以可以调用查询字段的方法id单个菜品的唯一标识如id 1ids多个菜品ID的集合如ids [1,2,3]dishId强调是菜品的ID通常用于关联查询如口味表、套餐表中的外键场景命名类型示例说明主键IDidLongdish.getId()实体类的主键字段外键IDxxxIdLongflavor.setDishId()指向其他表的外键批量主键idsListLongdeleteByIds(ids)多个主键的集合批量外键xxxIdsListLongdeleteByDishIds(dishIds)多个外键的集合遍历元素item名类型foreach中的item取决于业务含义对比维度ididsdishId数据类型Long单个数值ListLong集合Long单个数值英文含义单数一个ID复数多个ID单数菜品的ID数量1个N个≥11个所属表当前操作的表当前操作的表通常是外键指向dish表在SQL中的位置WHERE id #{id}WHERE id IN (1,2,3)WHERE dish_id #{dishId}业务含义当前表的主键当前表的主键集合菜品表的外键典型场景删除单个菜品批量删除菜品删除菜品的口味结语如果对你有帮助请点赞关注收藏你的支持就是我最大的动力