Java项目《苍穹外卖》day4实战练习,详细解析每一步!

 新增套餐

在进行业务开发前,我们要先研究一下接口文档

首先要明确该功能的请求路径以及请求方式,该功能是POST请求。

参数是放在请求体当中,意味着我们在控制层接收参数时要用@RequestBody注解。同时我们一定要注意,从接口文档里可以看出套餐里是附有若干个菜品的,因此我们新增套餐,不仅仅是新增套餐,也是新增菜品。通过对数据库表之间关系梳理,套餐里面的菜品是存入setmeal_dish表里,而不是dish表!

我们在controller.admin包下创建一个SetmealController的java类,即为负责套餐管理模块的控制层。在控制层,我们先写上一些必要的注解,

@RestController
@RequestMapping("/admin/setmeal")
@Api(tags = "套餐相关接口")
@Slf4j

public class SetmealController {
}
    

接下来在service包下创建一个服务层的接口

public interface SetmealService {
}

在impl包下创建一个java类来实现此接口:

@Service
@Slf4j
public class SetmealServiceImpl implements SetmealService {
}

别忘了在这个实现类里加上注解@Service,告诉Spring这里是负责服务层的代码。

在mapper包下创建SetmealMapper

@Mapper
public interface SetmealMapper {
}

同样别忘了@Mapper注解。这些必要的准备工作做好后,我们就可以开始写代码了。

首先在控制层中:

SetmealController.java

我们先从spring容器中注入SetmealService对象

@Autowired
    private SetmealService setmealService;

接下来我们就接收前端的请求:

@PostMapping
    @ApiOperation("新增套餐")
    public Result save(@RequestBody SetmealDTO setmealDTO) {
        log.info("新增套餐:{}",setmealDTO);

        setmealService.saveWithDish(setmealDTO);

        return Result.success();
    }

其中,第一行的注释@PostMapping表明负责前端的POST请求,@ApiOperation是Swagger的注解。由于新增套餐该功能不需要返回数据库的数据,因此该方法的返回类型为Result。我们用SetmealDTO来接收前端传来的数据,并在参数前面加上@RequestBody注解。我们加一句日志后,只需要直接调用服务层的方法即可。

SetmealServiceImpl.java

在对应的SetmealService接口要用有方法

//新增套餐和相应的菜品
     void saveWithDish(SetmealDTO setmealDTO);

 接下来我们就在实现类里重写该方法:

@Override
    @Transactional
    public void saveWithDish(SetmealDTO setmealDTO) {

        Setmeal setmeal = new Setmeal();
        BeanUtils.copyProperties(setmealDTO,setmeal);

        //向套餐插入数据
        setmealMapper.insert(setmeal);

        Long setmealId = setmeal.getId();

        //向菜品插入n条数据
        List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();

        if (setmealDishes != null && setmealDishes.size() > 0 ){
            setmealDishes.forEach(setmealDish -> {
                setmealDish.setSetmealId(setmealId);
            });
            setmealDishMapper.insertBatch(setmealDishes);

        }
    }

(以上代码是该方法的完整代码)

由于涉及到两张表的操作,我们在方法前面加一个@Transactional事务注解,保证两个表的操作能够同时成功或同时失败,而不至于拖泥带水。前面说到,新增套餐其实是两个操作,将套餐本身的信息插入setmeal表,还有将相应的菜品插入setmeal_dish表。所以我们创建一个Setmeal对象,将setmealDTO的属性通过BeanUtils.copyProperties()方法拷贝给setmeal,这样我们直接将setmeal对象作为参数传给操作setmeal数据库的持久层。

Setmeal setmeal = new Setmeal();
BeanUtils.copyProperties(setmealDTO,setmeal);

//向套餐插入数据
setmealMapper.insert(setmeal);

要注意,在setmeal_dish表中有字段名:setmeal_id。因此我们要获得setmeal的id。接下来我们完成向setmeal_dish表插入菜品的操作。因为一个套餐里有若干个菜品,所以我们用List集合来封装菜品:

List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();

然后我们遍历List集合,把setmealId赋值给每一个菜品(强调一下套餐里的菜品setmealDish和菜品Dish是两回事,不要混淆!)

if (setmealDishes != null && setmealDishes.size() > 0 ){
            setmealDishes.forEach(setmealDish -> {
                setmealDish.setSetmealId(setmealId);
            });

赋值后我们就可以把菜品插入setmeal_dish表里了:

setmealDishMapper.insertBatch(setmealDishes);

至此服务层的代码我们写好了。

接下来看持久层

SetmealMapper.java

@AutoFill(value = OperationType.INSERT)
void insert(Setmeal setmeal);

这里是插入套餐对应的持久层方法,由于涉及到的字段比较多,因此我们选择在xml文件里编写SQL语句,同时我们加上注解@AutoFill注解(这是自定义注解,在此不多赘述),为公共字段自动填充。

SetmealMapper.xml

<insert id="insert" parameterType="Setmeal" useGeneratedKeys="true" keyProperty="id">
        insert into setmeal(category_id, name, price, description, image, create_time, update_time, create_user, update_user)
        values
            (#{categoryId},#{name},#{price},#{description},#{image},#{createTime},#{updateTime},#{createUser},#{updateUser})
    </insert>

在这里要特别强调的是一定要加上useGeneratedKeys="true" keyProperty="id"。否则在服务层获取setmeal的id时就不能获取id,作者曾在这里卡了很久。

接下来我们看操作setmeal_dish表的持久层

SetmealDishMapper.java

void insertBatch(List<SetmealDish> setmealDishes);

由于涉及到动态SQL语句,所以我们依然在xml文件里编写SQL语句

SetmealDishMapper.xml

<insert id="insertBatch">
        insert into setmeal_dish(setmeal_id, dish_id, name, price, copies) values 
        <foreach collection="setmealDishes" item="setmealDish" separator=",">
            (#{setmealDish.setmealId},#{setmealDish.dishId},#{setmealDish.name},#{setmealDish.price},#{setmealDish.copies})
        </foreach>
    </insert>

因为需要插入表里的菜品有若干个,所以我们要用<foreach>标签将这些菜品一个个插入数据库中。

至此新增套餐的功能已完成。

套餐分页查询

分页查询和视频里之前做的菜品的分页查询区别不大,我们就不多讲述了。作者仅在需要注意的小坑再强调二三。

首先看一看接口文档,明确请求方式、请求路径、参数类型等

再看看返回数据格式

说明我们需要把数据库里的数据返回给前端。

SetmealController.java

    @GetMapping("/page")
    @ApiOperation("套餐分页查询")
    public Result<PageResult> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO) {
        log.info("套餐分页查询:{}",setmealPageQueryDTO);

        PageResult pageResult = setmealService.pageQuery(setmealPageQueryDTO);

        log.info("分页查询结果:{}",pageResult);

        return Result.success(pageResult);
    }

分页查询我们同样需要用到PageHelper插件,我们用SetmealPageQueryDTO类型来接收前端的请求参数。然后我们直接调用服务层即可,然后把pageResult返回给前端。

SetmealService.java

//套餐分页查询
PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);

然后在实现类里重写该方法:

     @Override
    public PageResult pageQuery(SetmealPageQueryDTO setmealPageQueryDTO) {
        PageHelper.startPage(setmealPageQueryDTO.getPage(), setmealPageQueryDTO.getPageSize());
        Page<SetmealVO> page = setmealMapper.pageQuery(setmealPageQueryDTO);

        return new PageResult(page.getTotal(), page.getResult());
    }

PageHelper分页需要两个参数,一个是页码,一个是单页展示数据总数,而这些在我们接收的前端数据里已经有了,所以我们对setmealPageQueryDTO调用getPage()和getPageSize方法即可。

因为查询出来的数据是多条的,所以我们用集合来接收持久层查出来的数据

Page<SetmealVO> page = setmealMapper.pageQuery(setmealPageQueryDTO);

服务层的代码完毕后,我们看持久层

SetmealMapper.java

Page<SetmealVO> pageQuery(SetmealPageQueryDTO setmealPageQueryDTO);

SetmealMapper.xml

我们在xml文件里编写SQL语句:

 <select id="pageQuery" resultType="com.sky.vo.SetmealVO">
        select setmeal.*,category.name as categoryName from setmeal left join category on
            setmeal.category_id = category.id
        <where>
            <if test="name != null ">
                and setmeal.name like concat('%',#{name},'%')
            </if>
            <if test="categoryId != null">
                and setmeal.category_id = #{categoryId}
            </if>
            <if test="status != null">
                and setmeal.status = #{status}
            </if>
        </where>
    </select>

特别强调!在SQL语句里,我们要查category.name的数据,没有问题,但如果直接这样写,而不加上as  categoryName那就出大问题了。

至于为什么,我们可以去SetmealVO类里去看看(我们是用SetmealVO来封装查询结果的)会发现类里有一个属性

     //分类名称
    private String categoryName;

它是用来封装category.name的,所以我们如果不在SQL语句给category.name起一个别名categoryName的话,那么查出来的分类名称根据就无法封装到SetmealVO的类里,进而就无法传送到前端,然后前端的结果就会是下图的样子:

 因此这里的SQL语句是一个坑,千万注意。

分页查询功能至此完毕。

为了完成删除套餐的功能,我们需要先完成启售、停售套餐的功能,因为根据业务需求,售卖中的套餐是不能直接删除的。

启售、停售套餐

启售停售套餐的功能比较简单,只是对数据库中status属性进行更新即可。因此这一模块作者不过多描述。

SetmealMapper.java

    @PostMapping("/status/{status}")
    @ApiOperation("启售、停售套餐")
    public Result startOrStop(@PathVariable Integer status,Long id) {
        log.info("启售、停售套餐:{},{}",status,id);

        setmealService.startOrStop(status,id);
        return Result.success();
    }

接口文档作者就不提供了,大家自己分析即可,主要就是明确几点:请求路径、请求参数、请求方式、返回数据。

SetmealServiceMapper.java

    //启售、停售套餐
    void startOrStop(Integer status, Long id);

同理,我们去实现类里重写该方法

SetmealServiceImpl.java

    @Override
    public void startOrStop(Integer status, Long id) {
        Setmeal setmeal = Setmeal.builder()
                .status(status)
                .id(id)
                .build();
        setmealMapper.update(setmeal);
    }

这里我们沿用视频里的风格,运用了建造者设计模式。将前端传来的id和status赋值给setmeal对象。然后调用持久层方法即可。

SetmealMapper.java

    @AutoFill(value = OperationType.UPDATE)
    void update(Setmeal setmeal);

在这里我们写一个通用的更新操作SQL语句,所以我们在xml文件里编写

SetmealMapper.xml

<update id="update">
        update setmeal
        <set>
            <if test="name != null ">name = #{name},</if>
            <if test="categoryId != null ">category_id = #{categoryId},</if>
            <if test="price != null ">price = #{price},</if>
            <if test="image != null ">image = #{image},</if>
            <if test="description != null ">description = #{description},</if>
            <if test="status != null ">status = #{status},</if>
            <if test="updateTime != null ">update_time = #{updateTime},</if>
            <if test="updateUser != null ">update_user = #{updateUser},</if>
        </set>
        where id = #{id}
    </update>

这样启售、停售的功能开发完成了。我们就可以着手完成删除套餐的功能了。

批量删除套餐

SetmealController.java

    @DeleteMapping
    @ApiOperation("批量删除套餐")
    public Result delete(@RequestParam List<Long> ids) {
        log.info("根据id批量删除套餐:{}",ids);

        setmealService.deleteBtach(ids);

        return Result.success();
    }

从接口文档可知,请求方式是DELETE方式,前端传来的参数id是有多个的,所以我们用List集合来接收参数,别忘了加上注解@RequestParam。然后直接调用服务层方法即可。

SetmealService.java

    //批量删除套餐
    void deleteBtach(List<Long> ids);

然后去实现类里重写该方法

SetmealServiceImpl.java

    @Override
    @Transactional
    public void deleteBtach(List<Long> ids) {
        //判断当前套餐是否在启售中,启售中不能删除
        for (Long id : ids) {
            Setmeal setmeal = setmealMapper.getById(id);
            if (setmeal.getStatus() == StatusConstant.ENABLE){
                throw new DeletionNotAllowedException(MessageConstant.SETMEAL_ON_SALE);
            }
        }

        //删除套餐表里的套餐
        setmealMapper.deleteByIds(ids);

        //删除套餐菜品表里的菜品
        setmealDishMapper.deleteByDishIds(ids);

    }

在处理业务的时候要主要业务逻辑,删除之前我们应该先判断一下套餐是否在停售状态,如果在停售状态,那么可以被删除;反之则不能被删除。所以我们循环遍历ids集合,对每一个id对应的套餐进行status的判断。经过判断后就说明都可以被删除。同时我们要注意,根据业务场景,删除一个套餐,那么对应的菜品都应该被删除,毕竟套餐里的菜品是存在setmeal_dish表里的,如果套餐删除了,而菜品没有删除,那么setmeal_dish里的菜品就没有任何意义了

所以我们分两步进行,第一步删除套餐,第二步删除对应的菜品。

先看看删除套餐

SetmealMapper.java

void deleteByIds(List<Long> ids);

这里涉及到动态SQL语句,因此我们在xml文件里编写

<delete id="deleteByIds">
        delete from setmeal where id in
        <foreach collection="ids" separator="," item="id" open="(" close=")">
            #{id}
        </foreach>
    </delete>

第一步完成了。接下来看删除对应的菜品

SetmealDishMapper.java

//根据套餐id批量删除
    void deleteByDishIds(List<Long> ids);

同样,我们在xml文件里编写SQL语句

<delete id="deleteByDishIds">
        delete from setmeal_dish where setmeal_id in
        <foreach collection="ids" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </delete>

至此删除套餐功能开发完毕

根据ID查询套餐

修改套餐可能会稍微麻烦一些,因为我们还要再另外实现前端页面回显的功能,也就是说我们还要实现一个根据套餐id查询的功能。我们先完成回显功能。

@GetMapping("/{id}")
    @ApiOperation("根据ID查询套餐")
    public Result<SetmealVO> getBySetmealId(@PathVariable Long id) {
        log.info("根据id查询套餐:{}",id);

        SetmealVO setmealVOs = setmealService.getById(id);

        return Result.success(setmealVOs);
    }

接口文档作者就不过多分析了。需要注意前端传来的是路径参数,因此要用@PathVariable注解。

直接调用服务层即可。需要注意我们还要用一个SetmealVO对象来封装查询的结果,然后返回给前端。

SetmealService.java

//根据id查询套餐
    SetmealVO getById(Long id);

SetmealServiceImpl.java

@Override
    public SetmealVO getById(Long id) {
        //根据id查询套餐
        Setmeal setmeal = setmealMapper.getById(id);

        //根据id查询对应菜品

        List<SetmealDish> setmealDishs = setmealDishMapper.getBySetmealId(id);

        //将结果封装到VO
        SetmealVO setmealVO = new SetmealVO();
        BeanUtils.copyProperties(setmeal,setmealVO);
        setmealVO.setSetmealDishes(setmealDishs);

        return setmealVO;
    }

在这里的业务逻辑是,查询套餐,不仅是查询套餐本身的信息,还要查询套餐对应的菜品,所以再业务层这里,我们要分成两步来进行。分别查到套餐和菜品后,我们将结果统一封装到SetmealVO对象里。先用BeanUtils.copyProperties()方法将查到的setmeal里的属性拷贝给setmealVO,此时setmealVO还需要菜品属性的值,所以我们就调用setSetmealDishes方法赋值。

分别来看两个查询操作

SetmealMapper.java

@Select("select * from setmeal where id = #{id};")
    Setmeal getById(Long id);

因为根据id查询套餐的SQL语句比较简单,所以我们直接用注解的方式来完成

接下来看查询菜品

SetmealDishMapper.java

 @Select("select * from setmeal_dish where setmeal_id = #{id}")
    List<SetmealDish> getBySetmealId(Long id);

同理,我们用注解的方式来完成。

至此根据ID查询套餐的功能完成了,接下来我们就可以实现修改套餐的功能了。

修改套餐

SetmealController.java

@PutMapping
    @ApiOperation("修改套餐")
    public Result update(@RequestBody SetmealDTO setmealDTO){
        log.info("修改套餐:{}",setmealDTO);

        setmealService.update(setmealDTO);

        return Result.success();
    }

控制层的代码作者就不描述了。

SetmealService.java

//修改套餐
    void update(SetmealDTO setmealDTO);

SetmealServiceImpl.java

@Override
    @Transactional
    public void update(SetmealDTO setmealDTO) {
        Setmeal setmeal = new Setmeal();
        BeanUtils.copyProperties(setmealDTO,setmeal);

        //修改套餐
        setmealMapper.update(setmeal);

        List<Long> ids = new ArrayList<>(); //存放套餐id的集合,这样可以不用新定义一个根据单个id查询的方法
        ids.add(setmealDTO.getId());

        //删除原有的菜品
        setmealDishMapper.deleteByDishIds(ids);
        List<SetmealDish> setmealDishes = setmealDTO.getSetmealDishes();

       //插入套餐对应的菜品
        
        if (setmealDishes != null && setmealDishes.size() > 0){
            setmealDishes.forEach(setmealDish -> {
                setmealDish.setSetmealId(setmealDTO.getId());
            });
        }

        //批量插入菜品
        setmealDishMapper.insertBatch(setmealDishes);
    }

修改套餐的业务逻辑稍微复杂一些,但和视频里之前对菜品进行修改的逻辑是一样的,所以我们直接照用即可。作者在这里有个地方用到的方法稍微有点不一样

List<Long> ids = new ArrayList<>(); //存放套餐id的集合,这样可以不用新定义一个根据单个id查询的方法
ids.add(setmealDTO.getId());

//删除原有的菜品
setmealDishMapper.deleteByDishIds(ids);

由于我们修改菜品是先将对应的菜品全部删除再插入修改后的菜品。所以我们要调用持久层的删除操作,在上面我们开发删除套餐功能的时候写过一个删除对应菜品的方法,但那个方法需要接收的是封装了套餐id的集合,而在此处我们只是一个Long类型的id参数,因此我们创建一个List集合,将这单个的id存进List集合去,这样我们就可以直接使用我们之前写的删除方法,从而避免了重新再去写一个根据单个id删除菜品的方法。

要注意我们要给每一个菜品的setmealId赋值

if (setmealDishes != null && setmealDishes.size() > 0){
            setmealDishes.forEach(setmealDish -> {
                setmealDish.setSetmealId(setmealDTO.getId());
            });
        }

然后我们直接调用持久层的方法来批量插入菜品数据。

SetmealDishMapper.java

void insertBatch(List<SetmealDish> setmealDishes);

同样我们在xml文件里编写

SetmealDishMapper.xml

<insert id="insertBatch">
        insert into setmeal_dish(setmeal_id, dish_id, name, price, copies) values 
        <foreach collection="setmealDishes" item="setmealDish" separator=",">
            (#{setmealDish.setmealId},#{setmealDish.dishId},#{setmealDish.name},#{setmealDish.price},#{setmealDish.copies})
        </foreach>
    </insert>

至此,修改套餐功能开发完毕。

  • 24
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值