苍穹外卖项目实战(day-4完整版)-记录实战教程及问题的解决方法
位置:sky-server/src/main/java/com/sky/service/impl/SetmealServiceImpl.java。位置:sky-server/src/main/java/com/sky/service/impl/SetmealServiceImpl.java。位置:sky-server/src/main/java/com/sky/service/impl/Setme
新增套餐功能
(14)SetmealController完善
位置:sky-server/src/main/java/com/sky/controller/admin/SetmealController.java
完整代码:
package com.sky.controller.admin;
import com.sky.dto.SetmealDTO;
import com.sky.result.Result;
import com.sky.service.SetmealService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 套餐管理
*/
@RestController
@RequestMapping("/admin/setmeal")
@Api(tags = "套餐相关接口")
@Slf4j
public class SetmealController {
@Autowired
private SetmealService setmealService;
/**
* 新增套餐
* @param setmealDTO
* @return
*/
@PostMapping
@ApiOperation("新增套餐")
public Result save(@RequestBody SetmealDTO setmealDTO) {
log.info("新增套餐:{}", setmealDTO);
setmealService.saveWithDish(setmealDTO);
return Result.success();
}
}
添加的代码:
/**
* 套餐管理
*/
@RestController
@RequestMapping("/admin/setmeal")
@Api(tags = "套餐相关接口")
@Slf4j
public class SetmealController {
@Autowired
private SetmealService setmealService;
/**
* 新增套餐
* @param setmealDTO
* @return
*/
@PostMapping
@ApiOperation("新增套餐")
public Result save(@RequestBody SetmealDTO setmealDTO) {
log.info("新增套餐:{}", setmealDTO);
setmealService.saveWithDish(setmealDTO);
return Result.success();
}
示意图:
(15)SetmealService完善
位置:sky-server/src/main/java/com/sky/service/SetmealService.java
完整代码:
package com.sky.service;
import com.sky.dto.SetmealDTO;
public interface SetmealService {
/**
* 保存套餐及其菜品
* @param setmealDTO
*/
void saveWithDish(SetmealDTO setmealDTO);
}
添加的代码:
/**
* 保存套餐及其菜品
* @param setmealDTO
*/
void saveWithDish(SetmealDTO setmealDTO);
示意图:
(16)SetmealServiceImpl完善
位置:sky-server/src/main/java/com/sky/service/impl/SetmealServiceImpl.java
完整代码:
package com.sky.service.impl;
import com.sky.dto.SetmealDTO;
import com.sky.entity.Setmeal;
import com.sky.entity.SetmealDish;
import com.sky.mapper.DishMapper;
import com.sky.mapper.SetmealDishMapper;
import com.sky.mapper.SetmealMapper;
import com.sky.service.SetmealService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Slf4j
public class SetmealServiceImpl implements SetmealService {
@Autowired
private SetmealMapper setmealMapper;
@Autowired
private SetmealDishMapper setmealDishMapper;
@Autowired
private DishMapper dishMapper;
/**
* 新增套餐,同时需要保存套餐和菜品的关联关系
* @param setmealDTO
*/
@Transactional
public void saveWithDish(SetmealDTO setmealDTO) {
Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealDTO, setmeal);
//向套餐表插入数据
setmealMapper.insert(setmeal);
//获取生成的套餐id
Long setmealId = setmeal.getId();
List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
setmealDishes.forEach(setmealDish -> {
setmealDish.setSetmealId(setmealId);
});
//保存套餐和菜品的关联关系
setmealDishMapper.insertBatch(setmealDishes);
}
}
示意图:
(17)SetmealMapper完善
位置:sky-server/src/main/java/com/sky/mapper/SetmealMapper.java
完整代码:
package com.sky.mapper;
import com.sky.annotation.AutoFill;
import com.sky.entity.Setmeal;
import com.sky.enumeration.OperationType;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface SetmealMapper {
/**
* 新增套餐
* @param setmeal
*/
@AutoFill(value = OperationType.INSERT)
void insert(Setmeal setmeal);
/**
* 根据分类id查询套餐的数量
* @param id
* @return
*/
@Select("select count(id) from setmeal where category_id = #{categoryId}")
Integer countByCategoryId(Long id);
}
添加的代码:
/**
* 新增套餐
* @param setmeal
*/
@AutoFill(value = OperationType.INSERT)
void insert(Setmeal setmeal);
示意图:
(18)SetmealDishMapper.java完善
位置:sky-server/src/main/java/com/sky/mapper/SetmealDishMapper.java
完整代码:
package com.sky.mapper;
import com.sky.entity.SetmealDish;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SetmealDishMapper {
/**
* 根据菜品id获取套餐id
* @param dishIds
* @return
*/
List<Long> getSetmealIdsByDishId(List<Long> dishIds);
/**
* 批量插入套餐菜品
* @param setmealDishes
*/
void insertBatch(List<SetmealDish> setmealDishes);
}
添加的代码:
/**
* 批量插入套餐菜品
* @param setmealDishes
*/
void insertBatch(List<SetmealDish> setmealDishes);
示意图:
(19)SetmealDishMapper.xml完善
位置:sky-server/src/main/resources/mapper/SetmealDishMapper.xml
完整代码:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.SetmealDishMapper">
<select id="getSetmealIdsByDishId" resultType="java.lang.Long">
SELECT setmeal_id FROM setmeal_dish WHERE dish_id IN
<foreach collection="dishIds" item="dishId" open="(" separator="," close=")">
#{dishId}
</foreach>
</select>
<insert id="insertBatch" parameterType="list">
insert into setmeal_dish
(setmeal_id,dish_id,name,price,copies)
values
<foreach collection="setmealDishes" item="sd" separator=",">
(#{sd.setmealId},#{sd.dishId},#{sd.name},#{sd.price},#{sd.copies})
</foreach>
</insert>
</mapper>
添加的代码:
<insert id="insertBatch" parameterType="list">
insert into setmeal_dish
(setmeal_id,dish_id,name,price,copies)
values
<foreach collection="setmealDishes" item="sd" separator=",">
(#{sd.setmealId},#{sd.dishId},#{sd.name},#{sd.price},#{sd.copies})
</foreach>
</insert>
示意图:
(20)功能测试
打上断点,调试项目(DuBug)
打开前端网页:添加套餐,添加相关数据,点击添加,回到控制台,查看是否成功接收到前端发送的信息,成功则如下图所示
跳过断点
查看数据库,已成功添加数据“套餐二”
至此,新增套餐功能已完成!
套餐分页查询功能
(21)SetmealController完善
位置:sky-server/src/main/java/com/sky/controller/admin/SetmealController.java
添加的代码:
/**
* 套餐分页查询
* @param setmealPageQueryDTO
* @return
*/
@GetMapping("/page")
@ApiOperation("分页查询")
public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO) {
PageResult pageResult = setmealService.pageQuery(setmealPageQueryDTO);
return Result.success(pageResult);
}
示意图:
(22)SetmealService完善
位置:sky-server/src/main/java/com/sky/service/SetmealService.java
添加的代码:
/**
* 分页查询套餐
* @param setmealPageQueryDTO
* @return
*/
PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);
示意图:
(24)SetmealServiceImpl完善
位置:sky-server/src/main/java/com/sky/service/impl/SetmealServiceImpl.java
添加的代码:
/**
* 分页查询
* @param setmealPageQueryDTO
* @return
*/
public PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO) {
int pageNum = setmealPageQueryDTO.getPage();
int pageSize = setmealPageQueryDTO.getPageSize();
PageHelper.startPage(pageNum, pageSize);
Page<SetmealVO> page = setmealMapper.pageQuery(setmealPageQueryDTO);
return new PageResult(page.getTotal(), page.getResult());
}
示意图:
(25)SetmealMapper.java完善
位置:sky-server/src/main/java/com/sky/mapper/SetmealMapper.java
添加的代码:
/**
* 分页查询
* @param setmealPageQueryDTO
* @return
*/
Page<SetmealVO> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);
示意图:
(26)SetmealMapper.xml完善
位置:sky-server/src/main/resources/mapper/SetmealMapper.xml
添加的代码:
<select id="pageQuery" resultType="com.sky.vo.SetmealVO">
select
s.*,c.name categoryName
from
setmeal s
left join
category c
on
s.category_id = c.id
<where>
<if test="name != null">
and s.name like concat('%',#{name},'%')
</if>
<if test="status != null">
and s.status = #{status}
</if>
<if test="categoryId != null">
and s.category_id = #{categoryId}
</if>
</where>
order by s.create_time desc
</select>
示意图:
(27)功能测试
调试项目,打开前端网页,找到一下位置
出现数据,则说明功能实现成功!
批量删除套餐功能实现
(28)SetmealServiceImpl完善
位置:sky-server/src/main/java/com/sky/service/impl/SetmealServiceImpl.java
添加的代码:
/**
* 批量删除套餐
* 1.删除套餐表数据
* 2.删除套餐菜品表数据
* 3.删除菜品表数据
* @param ids
*/
@Transactional//由于涉及到三个表的操作,需要事务控制
public void delete(List<Long> ids) {
//判断起售状态是否为0,如果为0,则不能删除
ids.forEach(id -> {
Setmeal setmeal = setmealMapper.getByID(id);
if (setmeal.getStatus() == StatusConstant.ENABLE) {
throw new DeletionNotAllowedException(MessageConstant.SETMEAL_ON_SALE);
}
});
//判断是否有菜品关联了套餐,如果有,则不能删除
ids.forEach(id -> {
setmealMapper.deleteByIDs(ids);//删除套餐表数据
setmealDishMapper.deleteBySetmealIds(ids);//删除套餐菜品表数据
});
}
示意图:
(29)SetmealController完善
位置:sky-server/src/main/java/com/sky/controller/admin/SetmealController.java
添加的代码:
/**
* 批量删除套餐
* @param ids
* @return
*/
@DeleteMapping
@ApiOperation("批量删除套餐")
public Result delete(@RequestParam List<Long> ids) {
log.info("批量删除套餐:{}", ids);
setmealService.delete(ids);
return Result.success();
}
示意图:
(30)SetmealService完善
位置:sky-server/src/main/java/com/sky/service/SetmealService.java
添加的代码:
/**
* 根据id删除套餐
* @param ids
*/
void delete(List<Long> ids);
示意图:
(31)SetmealDishMapper.xml完善
位置:sky-server/src/main/resources/mapper/SetmealDishMapper.xml
添加的代码:
<delete id="deleteBySetmealIds" parameterType="list">
DELETE FROM setmeal_dish
WHERE setmeal_id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<select id="getDishIdsBySetmealIds" resultType="java.lang.Long">
SELECT DISTINCT dish_id
FROM setmeal_dish
WHERE setmeal_id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
示意图:
(32)SetmealDishMapper.java完善
位置:sky-server/src/main/java/com/sky/mapper/SetmealDishMapper.java
添加的代码:
/**
* 根据套餐id删除套餐菜品
* @param ids
*/
void deleteBySetmealIds(List<Long> ids);
/**
* 根据套餐id获取菜品id
* @param ids
* @return
*/
List<Long> getDishIdsBySetmealIds(List<Long> ids);
示意图:
(33)SetmealMapper.java完善
位置:sky-server/src/main/java/com/sky/mapper/SetmealMapper.java
添加的代码:
/**
* 根据id删除套餐
* @param ids
*/
void deleteByIDs(List<Long> ids);
/**
* 根据id查询套餐
* @param id
* @return
*/
@Select("select * from setmeal where id = #{id}")
Setmeal getByID(Long id);
示意图:
(34)SetmealMapper.xml完善
位置:sky-server/src/main/resources/mapper/SetmealMapper.xml
添加的代码:
<delete id="deleteByIDs">
delete from setmeal
where id in
<foreach collection = "ids" item="id" index="index" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
示意图:
(35)功能测试
直接打开或调试项目,打开前端网页,选择需要删除的套餐,点击批量删除,删除成功则功能完成!
根据id获取套餐功能实现
(36)SetmealController
位置:sky-server/src/main/java/com/sky/controller/admin/SetmealController.java
添加的代码:
/**
* 根据id获取套餐
* @param id
* @return
*/
@GetMapping("/{id}")
@ApiOperation("根据id获取套餐")
public Result<SetmealVO> getById(@PathVariable("id") Long id){
log.info("根据id获取套餐:{}", id);
SetmealVO setmealVO = setmealService.getById(id);
return Result.success(setmealVO);
}
示意图:
(37)SetmealService
位置:sky-server/src/main/java/com/sky/service/SetmealService.java
添加的代码:
/**
* 根据id获取套餐详情
* @param id
* @return
*/
SetmealVO getById(Long id);
示意图:
(38)MessageConstant和SetmealServiceImpl
位置1:sky-common/src/main/java/com/sky/constant/MessageConstant.java
添加代码:
public static final String SETMEAL_NOT_FOUND = "套餐不存在";
示意图:
位置2:sky-server/src/main/java/com/sky/service/impl/SetmealServiceImpl.java
添加的代码:
/**
* 根据id查询套餐详情和菜品信息
* @param id
* @return
*/
public SetmealVO getById(Long id) {
//查询套餐信息,如果没有该菜品,则抛出异常
Setmeal setmeal = setmealMapper.getByID(id);
if (setmeal == null) {
throw new SetmealEnableFailedException(MessageConstant.SETMEAL_NOT_FOUND);
}
//查询套餐菜品信息
List<SetmealDish> setmealDishes = setmealDishMapper.getBySetmealId(id);
//创建套餐vo对象
SetmealVO setmealVO = new SetmealVO();
BeanUtils.copyProperties(setmeal, setmealVO);
//设置套餐菜品信息
setmealVO.setSetmealDishes(setmealDishes);
return setmealVO;
}
示意图:
(39)SetmealDishMapper.java
位置:sky-server/src/main/java/com/sky/mapper/SetmealDishMapper.java
添加的代码:
/**
* 根据id获取套餐及菜品关联关系
* @param id
* @return
*/
@Select("SELECT * FROM setmeal_dish WHERE id = #{id}")
List<SetmealDish> getBySetmealId(Long id);
示意图:
(40)功能测试
打开Swagger测试文档:苍穹外卖项目接口文档
查看数据库,找到套餐对应的id号,并输入到参数值中,如“32”,查看相应内容是否有对应的套餐信息,有则成功!
最后输入一个,数据库中没有的套餐id,如“1”,借此判断是否能正确抛出异常,
更新套餐功能
(41)SetmealController完善
位置:sky-server/src/main/java/com/sky/controller/admin/SetmealController.java
添加的代码:
/**
* 更新套餐
* @param setmealDTO
* @return
*/
@PutMapping
@ApiOperation("更新套餐")
public Result Update(@RequestBody SetmealDTO setmealDTO) {
log.info("更新套餐:{}", setmealDTO);
setmealService.update(setmealDTO);
return Result.success();
}
示意图:
(42)SetmealService完善
位置:sky-server/src/main/java/com/sky/service/SetmealService.java
添加的代码:
/**
* 更新套餐
* @param setmealDTO
*/
void update(SetmealDTO setmealDTO);
示意图:
(43)SetmealServiceImpl完善
位置:sky-server/src/main/java/com/sky/service/impl/SetmealServiceImpl.java
添加的代码:
/**
* 修改套餐
* @param setmealDTO
*/
@Transactional
public void update(SetmealDTO setmealDTO) {
Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealDTO, setmeal);
//1、修改套餐表,执行update
setmealMapper.update(setmeal);
//套餐id
Long setmealId = setmealDTO.getId();
//2、删除套餐和菜品的关联关系,操作setmeal_dish表,执行delete
setmealDishMapper.deleteBySetmealId(setmealId);
//获取套餐菜品dto列表
List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();
//执行循环,设置套餐id,然后保存到数据库,作用为保存套餐菜品的关联关系
setmealDishes.forEach(setmealDish -> {
setmealDish.setSetmealId(setmealId);
});
//3、重新插入套餐和菜品的关联关系,操作setmeal_dish表,执行insert
setmealDishMapper.insertBatch(setmealDishes);
}
示意图:
(44)SetmealDishMapper.java完善
位置:sky-server/src/main/java/com/sky/mapper/SetmealDishMapper.java
添加的代码:
/**
* 根据套餐id删除套餐菜品
* @param setmealId
*/
@Delete("delete from setmeal_dish where setmeal_id = #{setmealId}")
void deleteBySetmealId(Long setmealId);
示意图:
(45)功能测试
调试或者直接启动项目,打开前端网页,找到以下位置,任意选择一个套餐,点击“修改”,任意输入修改的数据,点击保存,跳出修改成功的信息,则说明功能完成!
查看数据库,数据库已发生变化
套餐停售启售功能
(1)SetmealController完善
位置:sky-server/src/main/java/com/sky/controller/admin/SetmealController.java
添加的代码:
/**
* 套餐起售停售
* @param status
* @param id
* @return
*/
@PostMapping("/status/{status}")
@ApiOperation("套餐起售停售")
public Result startOrStop(@PathVariable Integer status, Long id) {
setmealService.startOrStop(status, id);
return Result.success();
}
示意图:
(2)SetmealService完善
位置:sky-server/src/main/java/com/sky/service/SetmealService.java
添加的代码:
/**
* 套餐起售、停售
* @param status
* @param id
*/
void startOrStop(Integer status, Long id);
示意图:
(3)SetmealServiceImpl完善
位置:sky-server/src/main/java/com/sky/service/impl/SetmealServiceImpl.java
添加的代码:
/**
* 套餐起售、停售
* @param status
* @param id
*/
public void startOrStop(Integer status, Long id) {
//起售套餐时,判断套餐内是否有停售菜品,有停售菜品提示"套餐内包含未启售菜品,无法启售"
if(status == StatusConstant.ENABLE){
//select a.* from dish a, setmeal_dish b where a.id = b.dish_id and b.setmeal_id = #{id}
//如果有停售菜品,则抛出异常
List<Dish> dishList = dishMapper.getBySetmealId(id);
//判断是否有菜品关联了套餐,如果有,则不能删除
//套餐菜品为空,就不需要判断菜品状态了
if(dishList != null && dishList.size() > 0){
dishList.forEach(dish -> {
//判断菜品状态是否为0,如果为0,则不能启用
if(StatusConstant.DISABLE == dish.getStatus()){
//抛出异常
throw new SetmealEnableFailedException(MessageConstant.SETMEAL_ENABLE_FAILED);
}
});
}
}
//修改套餐状态
Setmeal setmeal = Setmeal.builder()
.id(id)
.status(status)
.build();
setmealMapper.update(setmeal);
}
部分代码说明:(AI)
//修改套餐状态
Setmeal setmeal = Setmeal.builder()
.id(id)
.status(status)
.build();
setmealMapper.update(setmeal);
在这段代码中,只设置 .id(id) 和 .status(status) 而不设置其他属性,主要基于以下原因:
- 更新需求的精准性
这段代码的核心目的是仅更新菜品的状态(起售 / 停售),不需要修改菜品的其他信息(如名称、价格、分类等)。
MyBatis 的 update 方法通常会根据实体对象中的非空字段生成 SQL 更新语句。如果设置了其他属性(如 name),即使没有修改其值,也可能被误更新(例如如果实体类中该字段默认值为 null,可能导致数据库字段被置空)。 - 避免意外数据覆盖
假设实体类 Dish 中还有 name、price 等属性,且这些属性在创建 dish 对象时未被赋值(默认可能为 null):- 如果 dishMapper.update(dish) 方法的 SQL 是 UPDATE dish SET name=?, price=?, status=? WHERE id=?,那么未设置的 name、price 会被更新为 null,这显然是错误的。
- 只设置 id(用于定位要更新的记录)和 status(要修改的字段),可以确保 SQL 只更新 status 字段,避免其他字段被意外修改。
- 符合 SQL 最佳实践
数据库更新操作应遵循最小修改原则:只更新需要变更的字段,这样可以:- 减少 SQL 语句的执行开销
- 避免触发不必要的索引更新或触发器
- 降低并发场景下的数据冲突风险
简单来说,这段代码通过 “只设置必要字段” 的方式,精准控制了更新范围,确保操作的安全性和高效性。
(4)DishMapper.java完善
位置:sky-server/src/main/java/com/sky/mapper/DishMapper.java
添加的代码:
/**
* 根据套餐id查询菜品
* sql语句的意思为根据传进来的id找到对应的套餐,
* 然后再根据该套餐表的dish_id找到对应菜品,最后找到套餐中菜品
* @param id
* @return
*/
@Select("select a.* from dish a, setmeal_dish b where a.id = b.dish_id and b.setmeal_id = #{id}")
List<Dish> getBySetmealId(Long id);
示意图:
SQL语句说明:根据指定的套餐 ID(setmeal_id),查询出该套餐所包含的所有菜品的完整信息
(5)功能测试
点击启售,成功后,再点击停售,若都无问题,则功能完成!
更多推荐
所有评论(0)