前言:
今日核心收获:完成了管理端最核心的两个模块——菜品管理和套餐管理。真正理解了 Spring Boot 分层架构中 Controller、Service、Mapper、DTO 各司其职的意义。更重要的是,今天踩的每一个坑,都是面试时可以讲出来的“实战经验”。
一、今日完成的功能模块
| 模块 | 完成的功能 |
|---|---|
| 分类管理 | 修改分类、删除分类(含关联校验) |
| 菜品管理 | 新增菜品(含口味)、分页查询、修改菜品、启售/停售、删除菜品 |
| 套餐管理 | 新增套餐(含关联菜品)、分页查询、修改套餐、启售/停售、删除套餐 |
| 全局异常处理 | 统一处理业务异常和数据库异常 |
| 图片上传下载 | 文件上传、下载、路径处理 |
二、今天学到的核心技术点(面试重点)
2.1 Spring Boot 分层架构
面试官常问:你能说一下项目中你是怎么分层的吗?每一层的作用是什么?
我的回答(以今天做的“新增菜品”为例):
| 层级 | 类的命名 | 今天做的事 | 一句话总结 |
|---|---|---|---|
| Controller | XxxController | 接收前端请求,调用 Service,返回结果 | 接客 |
| Service 接口 | XxxService | 定义业务方法 | 定规矩 |
| ServiceImpl | XxxServiceImpl | 写具体业务逻辑,加@Transactional | 办事 |
| Mapper | XxxMapper | 继承BaseMapper,执行 SQL | 跑腿 |
| Entity | Xxx | 和数据库表一一对应 | 表映射 |
| DTO | XxxDto | 继承 Entity,扩展字段 | 传 |
为什么要这么分层?
职责清晰:改 SQL 只动 Mapper,改业务逻辑只动 Service,改接口只动 Controller
便于测试:每一层都可以单独 Mock
事务管理:在 Service 层加
@Transactional,保证多表操作的一致性
2.2 DTO 的作用(今天最重要的理解)
面试官常问:你为什么用 DTO?直接用 Entity 不行吗?
我的回答:
今天做“新增菜品”时,前端提交的数据包含:
菜品基本信息(name、price、categoryId 等)
口味列表(flavors)
但Dish实体类里只有菜品基本信息,没有flavors字段。如果用 Entity 接收,口味数据就丢了。
解决方案:创建DishDto继承Dish,加上flavors字段。
public class DishDto extends Dish { private List<DishFlavor> flavors; // 新增时需要 private String categoryName; // 查询时需要 }DTO 的两个典型场景:
接收参数时:Entity 不够用,需要扩展字段
返回数据时:需要关联其他表的数据(如菜品列表要显示分类名称,而不是分类ID)
2.3 MyBatis-Plus 核心功能
| 功能 | 用法 | 今天用在哪 |
|---|---|---|
| 分页查询 | Page+MybatisPlusInterceptor | 菜品列表、套餐列表 |
| 条件构造器 | LambdaQueryWrapper | 根据分类ID查询菜品、根据名称模糊搜索 |
| 公共字段填充 | MetaObjectHandler+@TableField | 自动填充 createTime、updateTime、createUser、updateUser |
| 逻辑删除 | @TableLogic | 删除菜品/套餐时标记删除,不物理删除 |
2.4 全局异常处理
面试官常问:你的项目怎么处理异常的?如果用户删除一个正在售卖中的套餐,前端会看到什么?
我的回答:
我写了一个GlobalExceptionHandler,用@RestControllerAdvice统一处理所有异常。
@RestControllerAdvice public class GlobalExceptionHandler { // 处理业务异常(如:删除启售套餐) @ExceptionHandler(CustomException.class) public R<String> handleCustomException(CustomException ex) { return R.error(ex.getMessage()); // 返回:套餐正在售卖中,不能删除 } // 处理数据库唯一约束异常(如:重复的分类名称) @ExceptionHandler(SQLIntegrityConstraintViolationException.class) public R<String> handleSQLException(SQLIntegrityConstraintViolationException ex) { if (ex.getMessage().contains("Duplicate entry")) { return R.error("分类名称已存在"); } return R.error("未知错误"); } }这样设计的好处:
前端能收到友好的错误提示,而不是一堆看不懂的堆栈信息
代码复用:不用在每个 Controller 里写 try-catch
2.5 事务管理
面试官常问:新增菜品时要操作两张表,怎么保证数据一致性?
我的回答:
在 Service 实现类的方法上加@Transactional注解。
@Transactional public void saveWithFlavor(DishDto dishDto) { // 1. 保存菜品基本信息 this.save(dishDto); // 2. 保存菜品口味 dishFlavorService.saveBatch(dishDto.getFlavors()); }如果第二步失败了,第一步会自动回滚,不会出现“菜品保存成功但口味没保存”的情况。
三、今日遇到的错误及解决方案(面试加分项)
面试官非常看重你“踩过什么坑、怎么解决的”。以下每个错误都可以在面试时讲出来。
错误1:类型不匹配
现象:
不兼容的类型。实际为 'long',需要 'int'
代码:
int count = dishService.count(wrapper); // ❌原因:MyBatis-Plus 的count()方法返回的是long,不是int。
解决:
long count = dishService.count(wrapper); // ✅面试能说的:做分类删除时,需要先查询这个分类下有没有关联菜品。MyBatis-Plus 的count()返回long,但教材代码写的是int。这说明教材用的是旧版本,我用的是新版本。以后要注意看源码。
错误2:菜品列表显示分类ID而不是分类名称
现象:页面上“菜品分类”列显示的是1397844263642378242,不是“湘菜”。
原因:Dish实体类里只有categoryId,没有categoryName。前端需要显示名称。
解决:创建DishDto,增加categoryName字段。分页查询后,遍历数据,用categoryId去查询分类名称,设置进去。
面试能说的:这就是 DTO 的典型应用场景。Entity 只负责和数据库映射,DTO 负责和前端交互。用 DTO 的好处是:不污染 Entity,前端要什么字段我就给什么字段。
错误3:新增菜品时口味数据丢失
现象:菜品保存成功了,但口味没保存。
原因:前端提交的flavors数据无法被Dish实体类接收。
解决:在DishDto中增加List<DishFlavor> flavors字段。Service 层先保存菜品,拿到dishId,再为每个flavor设置dishId,最后批量保存。
面试能说的:这是一个典型的“一对多”场景。一张菜品表对应多张口味表。DTO 在这里起到了“聚合数据”的作用。
错误4:图片上传后不显示
现象:上传图片成功,文件夹里也有文件,但页面不显示。
原因:路径拼接缺少分隔符。
错误代码:
new File(basePath + fileName); // 结果:D:/img八宝粥.png正确代码:
new File(basePath + File.separator + fileName); // 结果:D:/img/八宝粥.png面试能说的:这是一个非常细节的 Bug。Windows 和 Linux 的路径分隔符不一样,用File.separator可以跨平台。以后写文件路径都要养成这个习惯。
错误5:删除启售套餐返回500,不是业务提示
现象:控制台打印了“套餐正在售卖中,不能删除”,但前端显示 500 错误。
原因:GlobalExceptionHandler里没有处理CustomException。
解决:添加@ExceptionHandler(CustomException.class)方法。
面试能说的:这说明全局异常处理不完整。写完异常处理器后,要记得测试所有自定义异常是否都能被正确捕获。这个问题暴露了我对全局异常处理理解不够全面。
四、今天的内容对后端开发的重要性
| 知识点 | 企业招聘看重程度 | 说明 |
|---|---|---|
| 分层架构 | ⭐⭐⭐⭐⭐ | 必考。每个后端岗位都会问 |
| DTO 设计 | ⭐⭐⭐⭐ | 体现分层思想,实习生会这个很加分 |
| MyBatis-Plus 使用 | ⭐⭐⭐⭐ | 大部分公司用 MP 或 JPA |
| 事务管理 | ⭐⭐⭐⭐⭐ | 涉及多表操作、金钱相关,必须掌握 |
| 全局异常处理 | ⭐⭐⭐⭐ | 体现代码规范,实习生能写出来很加分 |
| 文件上传下载 | ⭐⭐⭐ | 常见功能,但不是核心 |
是否涉及后端核心?
✅是的,今天的内容覆盖了 Java 后端开发的 80% 核心:
数据库 CRUD
多表操作
事务管理
统一异常处理
分层架构设计
唯一缺的:微服务、消息队列、高并发(这些是高级阶段,实习生不会也正常)。
五、项目运行截图
菜品管理页面(分页 + 图片显示)
访问
http://localhost:8080/backend/index.html→ 菜品管理显示 10 条菜品信息(八宝粥、王老吉、米饭、北冰洋、上汤焗龙虾、老火靓汤、清蒸河鲜海鲜、脆皮乳鸽、烤乳猪、白切鸡)
图片正常显示
每条记录有:修改、停售、删除按钮
底部显示:共 24 条,10 条/页
套餐管理页面
访问套餐管理页面
显示超值幼儿园大餐、大老板超值套餐
图片正常显示
每条记录有:修改、停售、删除按钮
六、今日总结
今天最大的收获不是“写完了代码”,而是真正理解了每一层为什么存在。
写简历时,我可以这样描述:
“负责瑞吉外卖后台的菜品管理和套餐管理模块,基于 Spring Boot + MyBatis-Plus 实现。通过 DTO 解决多表关联查询和参数接收问题,使用全局异常处理器统一返回业务提示,通过事务注解保证多表操作的一致性。项目中遇到并解决了类型匹配、路径分隔符、全局异常处理不完整等问题。”
面试官看到这段描述,会问什么?
你为什么要用 DTO?
你的全局异常处理器是怎么设计的?
你是怎么保证多表操作的一致性的?
你遇到过什么难忘的问题?
今天的内容,足够回答这些问题。