

🔥个人主页:北极的代码(欢迎来访)
🎬作者简介:java后端学习者
✨命运的结局尽可永在,不屈的挑战却不可须臾或缺!
前言:前面我们完成了新增菜品的业务功能,这里我们将继续完成项目,实现菜品的分页查询功能,和删除菜品的业务功能,提供详细的代码讲解和逻辑解释。
分页查询:
分析产品原型:
┌─────────────────────────────────────────────────────────────┐
│ 菜品管理 > 菜品列表 [+新增菜品] │
├─────────────────────────────────────────────────────────────┤
│ [菜品名称] [分类选择▼] [状态▼] [查询] [重置] │
├─────────────────────────────────────────────────────────────┤
│ □ 菜品图片 菜品名称 分类 价格 状态 最后更新时间 操作 │
├─────────────────────────────────────────────────────────────┤
│ □ [图片] 宫保鸡丁 川菜 ¥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)两个核心信息,前端拿到后就能知道总共有多少条数据、当前页显示哪些数据。
java
@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)
它是连接数据库原始数据和前端展示界面的桥梁,是分页查询中最核心的数据载体。
java
/**
* 菜品分页查询
* @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?
AOP思想:PageHelper通过拦截器实现关注点分离,业务代码不需要关心分页细节
自动计算:自动计算偏移量(offset)并拼接LIMIT子句
自动COUNT:自动执行COUNT查询获取总记录数
自动包装:自动将List包装成Page对象,提供分页信息
统一处理:所有分页查询使用相同的方式,代码更简洁
PageHelper的魔法本质:
startPage():将分页参数存入ThreadLocal
拦截器:拦截SQL执行,从ThreadLocal读取参数
SQL改造:自动添加COUNT和LIMIT
结果包装:返回包含分页信息的Page对象
java
<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的底层执行:
java
// 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:
java
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
java
package com.sky.service;
public interface DishService {
/**
* 批量删除菜品
* @param ids 菜品ID列表
*/
void deleteBatch(List<Long> ids);
}
/**
* 解析:
* 1. 定义业务接口,明确功能
* 2. 批量删除方法,接收ID列表
*/
Service层实现 - 核心业务逻辑
java
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层 - 数据访问
java
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,通常用于关联查询(如口味表、套餐表中的外键)
| 场景 | 命名 | 类型 | 示例 | 说明 |
|---|---|---|---|---|
| 主键ID | id | Long | dish.getId() | 实体类的主键字段 |
| 外键ID | xxxId | Long | flavor.setDishId() | 指向其他表的外键 |
| 批量主键 | ids | List<Long> | deleteByIds(ids) | 多个主键的集合 |
| 批量外键 | xxxIds | List<Long> | deleteByDishIds(dishIds) | 多个外键的集合 |
| 遍历元素 | item名 | 类型 | foreach中的item | 取决于业务含义 |
| 对比维度 | id | ids | dishId |
|---|---|---|---|
| 数据类型 | 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} |
| 业务含义 | 当前表的主键 | 当前表的主键集合 | 菜品表的外键 |
| 典型场景 | 删除单个菜品 | 批量删除菜品 | 删除菜品的口味 |
结语:如果对你有帮助,请,**点赞,关注,收藏,**你的支持就是我最大的动力!!