苍穹外卖-day03-新增菜品模块学习笔记(含MyBatis XML映射格式)
【作者说:在一段小模块的学习中,我通常会以小见大,在这篇笔记中,我通过学习新增菜品了解到开发过程的先后顺序,我认为这很重要,对于一个没工作经历的在校学生来讲,我们可以通过对这个项目一步步的学习来了解一个公司基本的开发逻辑,主要是对三个层的了解(控制层(Controller),业务层(Service),数据访问层(Mapper))。在目前的AI时代,学习一些代码细节是完全没有必要的,vibe coding的魅力就在于人来思考,AI来执行。我将会继续更新之后的项目内容,说说AI时代我的想法,感谢点赞收藏,关注我获取更多精彩内容!!!】
一、模块整体核心架构
本次学习的新增菜品模块,是苍穹外卖菜品管理核心功能模块,整体采用前后端分离 + SpringBoot 整合 MyBatis架构模式,后端代码严格遵循控制层 - 业务层 - 数据访问层的分层开发思想,各层分工明确、相互协作,完成菜品数据从前端接收到数据库存储的全流程。
整体涉及核心模块/文件分类
- 控制层(Controller)
- DishController :菜品专属控制类,接收前端发送的新增菜品请求、处理请求参数
- CommonController :通用控制类,封装文件上传功能(支持图片、文件等通用上传,不局限于菜品图片,实现代码复用)------传文件单独的类
- 业务层(Service)------先写接口,在写实现方式
- DishService :菜品业务接口,定义新增菜品的业务方法规范
- DishServiceImpl :菜品业务实现类,实现 DishService 接口,编写新增菜品核心业务逻辑
- 数据访问层(Mapper)-------映射两个接口,MyBatis XML 格式
- DishMapper 、 DishFlavorMapper (接口):数据访问接口,定义数据库操作方法
- Mapper XML 映射文件:存放于 resources/mapper 目录下,采用 MyBatis XML 格式,编写 SQL 语句,实现 Java 代码与数据库的交互,无需编写 JDBC 原生代码,直接在 XML 中编写 SQL 完成数据库增删改查
二、核心知识点梳理
- MyBatis 映射文件核心作用
Mapper XML 映射文件是 MyBatis 的核心配置,通过 XML 格式绑定对应的 Mapper 接口,将 SQL 语句与 Java 方法解耦,可以直接在 XML 中编写标准 SQL 语句,实现后端 Java 代码对数据库的操作,无需在 Java 类中拼接 SQL,让代码更简洁、便于维护。
- 核心注解:@Autowired
本次学习重点掌握 @Autowired 依赖注入注解,其核心作用是: 无需手动 new 创建对象,通过 Spring 框架自动完成对象的创建、装配,实现不同类之间的依赖绑定与调用,让两个互不相关的类可以直接调用对方的方法,简化对象管理,降低代码耦合度。 例如:在 Controller 中注入 Service 对象、在 ServiceImpl 中注入 Mapper 对象,直接调用对应方法,无需手动实例化。
- 通用文件上传功能(CommonController)
文件上传属于通用功能,不单单服务于菜品图片上传,因此单独抽取为 CommonController ,实现代码复用。 该模块主要处理前端上传的图片/文件,完成文件接收、格式处理、存储等逻辑,为新增菜品时的菜品图片上传提供接口支持。
- 前端请求交互
新增菜品前端页面,主要传递菜品名称、菜品种类、菜品价格、菜品图片、菜品描述、菜品口味等参数,通过接口请求发送到后端 DishController ,后端分层处理后,将数据最终存入数据库。
三、新增菜品模块开发先后顺序-----由此掌握企业基础开发逻辑
按照从下到上、逐层实现的开发逻辑,配合 Alt + Enter 快捷键快速补全代码,具体顺序如下:
开发遵循从底层到上层的顺序,因为上层代码需要调用下层代码,必须先写被依赖的部分,否则会报错。
- 第一步:设计数据库表并创建对应实体类 先确定菜品、口味的数据表结构,再编写与之对应的 Java 实体类,作为整个模块的数据基础。
- 第二步:开发数据访问层(Mapper) 编写 Mapper 接口与 MyBatis XML 映射文件,实现数据库增删改查操作。 逻辑:Service 层需要调用 Mapper 操作数据库,因此 Mapper 必须优先开发。
- 第三步:开发业务层(Service) 编写 Service 接口与实现类,注入 Mapper 并封装业务逻辑(如同时保存菜品与口味)。 逻辑:Controller 要调用 Service 处理业务,所以 Service 在 Mapper 之后、Controller 之前。
- 第四步:开发通用控制层(CommonController) 实现文件/图片上传通用接口,为菜品图片上传提供支持,属于通用辅助功能。
- 第五步:开发菜品控制层(DishController) 定义接口地址,接收前端请求,调用 Service 完成新增逻辑并返回结果。 逻辑:Controller 是最上层入口,依赖 Service,因此放在最后开发
四、核心调用逻辑
前端发起新增菜品请求 → DishController 接收请求 → 调用 DishService 接口方法 → DishServiceImpl 执行业务逻辑、调用 Mapper 接口 → Mapper XML 执行 SQL 语句 → 数据写入数据库 → 逐层返回结果给前端,完成新增菜品流程。
五、MyBatis XML映射文件核心重点小结
- 基础格式核心规则
- 文件头部有固定声明,是MyBatis XML映射文件的标识,直接复用即可
- namespace是核心绑定项,必须和对应Mapper接口的全限定类名完全一致,实现接口与XML文件的绑定
- 不同数据库操作对应专属 负责删除
- 关键参数与语法要点
- parameterType :指定传入接口的参数类型,保证参数传递匹配
- 新增操作获取自增主键:通过 useGeneratedKeys="true" 配合 keyProperty 属性,拿到数据库自动生成的主键值
- 参数传递:使用 #{实体类属性名} ,自动对应注入参数,避免SQL注入
- 批量操作`标签实现批量数据插入/处理,简化批量业务代码
- 编写核心原则
- SQL语句与Java代码完全分离,便于后期维护和SQL优化
- 接口方法名与XML标签id保持一致,一一对应
- 遵循MyBatis规范编写,保证数据库交互的稳定性
4.关键代码展示
//新增菜品MyBatis XML映射
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into dish (name, category_id, price, image, description, status, create_time,
update_time, create_user, update_user)
values (#{name}, #{categoryId}, #{price}, #{image}, #{description}, #{status},
#{createTime}, #{updateTime}, #{createUser}, #{updateUser})
</insert>
菜品口味映射与上述内容差不多,就不做过多阐释。
六、顺序展开新增菜品开发(仅以dish为例)
1.设计数据库表并创建实体类
设计数据库表:
创建实体类:(仅以DTO为例)
DTO:后端接受前端数据的请求,前端传回的数据的长度,类型和格式都要与后端一一对应 VO:前端接受后端数据的请求,后端要严格按照前端的数据格式进行“打包”,通常为JSON格式
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Dish implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//菜品名称
private String name;
//菜品分类id
private Long categoryId;
//菜品价格
private BigDecimal price;
//图片
private String image;
//描述信息
private String description;
//0 停售 1 起售
private Integer status;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private Long createUser;
private Long updateUser;
//公共字段
}
2.MyBatis XML映射mapper接口文件----与mapper接口一一对应
//新增菜品MyBatis XML映射
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into dish (name, category_id, price, image, description, status, create_time,
update_time, create_user, update_user)
values (#{name}, #{categoryId}, #{price}, #{image}, #{description}, #{status},
#{createTime}, #{updateTime}, #{createUser}, #{updateUser})
</insert>
3.开发业务层(Service)
Service接口:
public interface DishService {
public void saveWithFlavor(DishDTO dishDTO); //新增菜品和对应的口味
PageResult page(DishPageQueryDTO dishPageQueryDTO);//菜品分页查询
DishVO getById(Long id);//根据id查询菜品
void update(DishDTO dishDTO);//修改菜品信息
void delete(List<Long> ids);//批量删除菜品
List<DishVO> list(Long categoryId);//根据分类id查询菜品列表
void startOrStop(Integer status, Long id);//菜品起售与停售
List<DishVO> listWithFlavor(Dish dish);//条件查询菜品和口味
}
Service接口实现方法(主要与口味进行绑定):
@Service
public class DishServiceImpl implements DishService {
@Resource
private DishMapper dishMapper;
@Resource
private DishFlavorMapper dishFlavorMapper;
/**
* 新增菜品和对应的口味
*
* @param dishDTO 菜品DTO对象,包含菜品基本信息和口味列表
*/
@Override
@Transactional(rollbackFor = Exception.class) // 增加rollbackFor,更严谨
public void saveWithFlavor(DishDTO dishDTO) {
// 1. 创建Dish实体对象,用于接收DTO数据并插入数据库
Dish dish = new Dish();
// 将DTO中的属性复制到实体对象中
BeanUtils.copyProperties(dishDTO, dish);
// 2. 插入菜品基本信息到dish表,并自动生成主键id
dishMapper.insert(dish);
// 3. 处理口味数据
List<DishFlavor> flavors = dishDTO.getFlavors();
if (flavors != null && !flavors.isEmpty()) {
// 为每个口味设置对应的菜品id
flavors.forEach(flavor -> flavor.setDishId(dish.getId()));
// 4. 批量插入口味信息到dish_flavor表
dishFlavorMapper.insertBatch(flavors);
}
}
}
4.开发通用控制层(CommonController)----上传图片等文件
public class CommonController {
@Autowired//自动装配,代替new,直接将AliOssUtil注入到当前类里,可以在后面直接使用
private AliOssUtil aliOssUtil;
@PostMapping("/upload")
@ApiOperation("文件上传")
public Result<String> upload(MultipartFile file) {
log.info("文件上传");
String originalFilename =file.getOriginalFilename();
//截取后缀
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
String objectName = UUID.randomUUID().toString() + suffix;//拼接文件名
String url = null;
try {
url = aliOssUtil.upload(file.getBytes(), objectName);
} catch (IOException e) {
log.error("文件上传失败");
return Result.error(UPLOAD_FAILED);
}
return Result.success(url);
}
}
5.开发菜品控制层(DishController)
public Result save(@RequestBody DishDTO dishDTO){
log.info("新增菜品");
dishService.saveWithFlavor(dishDTO);
cleanCache("dish_"+dishDTO.getCategoryId());
return Result.success();
}
七、新增菜品模块开发总结
本次新增菜品模块开发,完整落地了Spring Boot+MyBatis的分层开发范式,严格遵循Controller→Service→Mapper的职责划分,通过DTO/Entity/VO实现数据隔离与流转。核心依托 @Transactional 保障菜品与口味表的事务一致性,基于MyBatis映射文件完成SQL解耦,复用通用文件上传能力实现图片存储。开发流程从表设计、实体创建,到接口定义、业务实现、控制层开发闭环,既掌握了分层架构思想,也沉淀了可复用的业务开发模板,为后续模块开发奠定了规范基础。