当前位置: 首页 > news >正文

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

🔥个人主页:北极的代码(欢迎来访)
🎬作者简介: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│ │ │ ?page=1&pageSize=10 │ │ │ &name=宫保&status=1 │ │ │────────────────────>│ │ │ │ │ │ │ 4. 接收参数 │ │ │ DishPageQueryDTO │ │ │────────────────────>│ │ │ │ │ │ 5. 调用Service │ │ │ pageQuery() │ │ │────────────────────>│ │ │ │ │ │ │ 6. 执行SQL │ │ │ SELECT... │ │ │<───┘ │ │ 7. 返回PageResult │ │ │ (total+records) │ │ │<────────────────────│ │ │ │ │ 8. 返回JSON数据 │ │ │ <────────────────────│ │ │ │ │ │ 9. 渲染表格和分页 │ │ │────────────────────>│ │

代码实现:

Controller:

pageResult是后端封装分页查询结果的统一对象,它包含了总记录数(total)当前页数据列表(records)两个核心信息,前端拿到后就能知道总共有多少条数据、当前页显示哪些数据。

@GetMapping("/page") @ApiOperation("菜品分页查询") public Result<PageResult> page (DishPageQueryDTO dishPageQueryDTO){ log.info("菜品分页查询,#{}",dishPageQueryDTO); PageResult pageResult= dishService.pageQuery(dishPageQueryDTO); return Result.success(pageResult);

Service:

PageHelper.startPage
  • 作用:像一个"开关",开启分页功能

  • 原理:将分页参数存入ThreadLocal(当前线程的私有存储空间)

  • 为什么放在这里:必须在查询语句之前执行,才能拦截即将执行的SQL

dishMapper.pageQuery()—— 执行查询

  • 作用:看似普通查询,实则被PageHelper增强

  • 原理:PageHelper拦截器会自动修改SQL,添加LIMIT子句

  • 为什么返回Page:PageHelper自动将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()); Page<DishVO> page=dishMapper.pageQuery(dishPageQueryDTO); return new PageResult(page.getTotal(),page.getResult());

Mapper:

为什么不需要手动拼接page和pageSize?

  1. AOP思想:PageHelper通过拦截器实现关注点分离,业务代码不需要关心分页细节

  2. 自动计算:自动计算偏移量(offset)并拼接LIMIT子句

  3. 自动COUNT:自动执行COUNT查询获取总记录数

  4. 自动包装:自动将List包装成Page对象,提供分页信息

  5. 统一处理:所有分页查询使用相同的方式,代码更简洁

PageHelper的魔法本质

  • startPage():将分页参数存入ThreadLocal

  • 拦截器:拦截SQL执行,从ThreadLocal读取参数

  • SQL改造:自动添加COUNT和LIMIT

  • 结果包装:返回包含分页信息的Page对象

    <select id="pageQuery" resultType="com.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 test="name!=null"> and d.name like concat('%',#{name},'%') </if>> <if test="categoryId!=null"> and d.category_id=#{categoryId} </if>> <if test="status!=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表

    实现流程:

    Controller:

    package com.sky.controller.admin; /** * 菜品管理Controller * 接收前端删除菜品的请求 */ @RestController @RequestMapping("/admin/dish") @Api(tags = "菜品管理") @Slf4j public class DishController { @Autowired private DishService dishService; /** * 删除菜品(支持批量) * @param ids 菜品ID列表,例如:ids=1,2,3 * @return */ @DeleteMapping @ApiOperation("删除菜品") public Result delete(@RequestParam List<Long> ids) { log.info("删除菜品,ids:{}", ids); // 调用Service层执行业务 dishService.deleteBatch(ids); return Result.success(); } }
    /** * 解析: * 1. @DeleteMapping:处理HTTP DELETE请求 * 2. @RequestParam List<Long> ids:接收URL参数,如 /admin/dish?ids=1,2,3 * 3. 直接调用Service,Controller不处理业务逻辑 */

    Service

    package com.sky.service; public interface DishService { /** * 批量删除菜品 * @param ids 菜品ID列表 */ void deleteBatch(List<Long> 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(List<Long> ids) { log.info("批量删除菜品,ids:{}", ids); // ========== 第1步:参数校验 ========== if (ids == null || ids.isEmpty()) { throw new BusinessException("请选择要删除的菜品"); } // ========== 第2步:检查菜品状态(是否起售) ========== // 根据ID列表查询所有菜品信息 List<Dish> dishList = dishMapper.selectByIds(ids); for (Dish dish : dishList) { if (dish.getStatus() == 1) { // 1表示起售 throw new BusinessException("菜品【" + dish.getName() + "】正在起售中,不能删除"); } } // ========== 第3步:检查是否被套餐关联 ========== // 根据菜品ID查询关联的套餐ID List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(ids); if (setmealIds != null && setmealIds.size() > 0) { // 查询套餐名称用于提示 List<Setmeal> 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 菜品列表 */ List<Dish> selectByIds(@Param("ids") List<Long> ids); /** * 根据ID列表批量删除菜品 * @param ids 菜品ID列表 */ void deleteByIds(@Param("ids") List<Long> ids); }

    注意事项:

    根据主键 id查询菜品的起售状态,主键id返回所有的字段,所以可以调用查询字段的方法

    • id:单个菜品的唯一标识(如:id = 1

    • ids:多个菜品ID的集合(如:ids = [1,2,3]

    • dishId:强调是菜品的ID,通常用于关联查询(如口味表、套餐表中的外键)

    场景命名类型示例说明
    主键IDidLongdish.getId()实体类的主键字段
    外键IDxxxIdLongflavor.setDishId()指向其他表的外键
    批量主键idsList<Long>deleteByIds(ids)多个主键的集合
    批量外键xxxIdsList<Long>deleteByDishIds(dishIds)多个外键的集合
    遍历元素item名类型foreach中的item取决于业务含义
    对比维度ididsdishId
    数据类型Long(单个数值)List<Long>(集合)Long(单个数值)
    英文含义单数:一个ID复数:多个ID单数:菜品的ID
    数量1个N个(≥1)1个
    所属表当前操作的表当前操作的表通常是外键,指向dish表
    在SQL中的位置WHERE id = #{id}WHERE id IN (1,2,3)WHERE dish_id = #{dishId}
    业务含义当前表的主键当前表的主键集合菜品表的外键
    典型场景删除单个菜品批量删除菜品删除菜品的口味

    结语:如果对你有帮助,请,点赞,关注,收藏,你的支持就是我最大的动力!!

    http://www.jsqmd.com/news/453860/

    相关文章:

  • MATLAB疑难杂症全攻略:从诊断到优化
  • 代码随想录总结
  • Python中sorted函数与lambda表达式的详细解析
  • JavaWeb开发环境配置
  • [特殊字符] GitHub热榜 - DashengTokenizer音频革命
  • 5G天车数据采集物联网解决方案
  • 强烈安利! 专科生专属降AIGC平台 千笔·降AIGC助手 VS 知文AI
  • 工业组态网关通过MQTT协议实现数据集成监控
  • Docker安装Code-Server (在线编程)
  • [特殊字符] 视觉深度估计算法新突破 _ Intel DPT-BEIT大模型解析
  • rk3576(4)之buildroot将自定义Qt程序编译进系统
  • 广东犸力:以高频均匀压力传感器技术,深度绑定工业热点场景 - 速递信息
  • [C语言] 回溯算法的使用
  • 《锁饥饿:能抢却抢不到的并发困境》
  • Timestamp.cc和Timestamp.h文件分析
  • 2026 低压高低氧舱品牌推荐:优质厂家、靠谱公司、实力对比全解析 - 品牌推荐大师1
  • 部委政务安全智能运营未来五年发展规划与工作思路
  • 47.102.113.21
  • AI coding上手之OpenClaw快速上手
  • 【刘二大人】《PyTorch深度学习实践》——反向传播代码(自用)
  • 导师推荐 9个AI论文软件:自考毕业论文+开题报告写作全测评
  • 解读代码Dftpav-main(3.1规划核心traj_server_ros.cpp26.3.8)
  • Linux:网络编程-基于HTTP协议的天气预报查询系统开发详解
  • Kafka自动提交把消息吃了:一次“已提交未处理”+重平衡导致丢数和爆堆积
  • 把 AI助手搬进飞书!OpenClaw接入完整指南
  • 2026广州GEO优化公司排名TOP5|本地实力派盘点,亚森SEO稳居榜首!
  • 周红伟:2026年OpenClaw最佳实践:一键部署+免费API配置+集成8大股票分析Skills及避坑指南
  • matlab麻雀搜索算法(SSA)优化BP神经网络,权值和阈值,一个压缩包共三个文件,包括有数...
  • 深度学习在财务报表舞弊识别中的应用:构建一个智能审计助手
  • Rokid UXR 的手势追踪虚拟中更真实的手实战开发【含 工程源码 和 最终完成APK】