SpringBoot新增菜品模块开发(事务管理+批量插入+主键回填)

需求分析与设计

一:产品原型

后台系统中可以管理菜品信息,通过 新增功能来添加一个新的菜品,在添加菜品时需要选择当前菜品所属的菜品分类,并且需要上传菜品图片。

新增菜品原型:

当填写完表单信息, 点击"保存"按钮后, 会提交该表单的数据到服务端, 在服务端中需要接受数据, 然后将数据保存至数据库中。

业务规则:

  • 菜品名称必须是唯一的

  • 菜品必须属于某个分类下,不能单独存在

  • 新增菜品时可以根据情况选择菜品的口味

  • 每个菜品必须对应一张图片

二:接口设计

根据上述原型图先**粗粒度**设计接口,共包含3个接口。

  • 根据类型查询分类(已完成)

  • 文件上传(已完成)

  • 新增菜品

接下来明确接口的请求方式、请求路径、传入参数和返回值。

口味非必须,且是object[]数组,可以传多个口味(一个菜品可以对应多种口味)

所以后端使用集合封装

三:数据表设计

新增菜品,其实就是将新增页面录入的菜品信息插入到dish表,如果添加了口味做法,还需要向dish_flavor表插入数据。所以在新增菜品时,涉及到两个表:

表名 说明
dish 菜品表
dish_flavor 菜品口味表

1). 菜品表:dish

字段名 数据类型 说明 备注
id bigint 主键 自增
name varchar(32) 菜品名称 唯一
category_id bigint 分类id 逻辑外键
price decimal(10,2) 菜品价格
image varchar(255) 图片路径
description varchar(255) 菜品描述
status int 售卖状态 1起售 0停售
create_time datetime 创建时间
update_time datetime 最后修改时间
create_user bigint 创建人id
update_user bigint 最后修改人id

2). 菜品口味表:dish_flavor

字段名 数据类型 说明 备注
id bigint 主键 自增
dish_id bigint 菜品id 逻辑外键
name varchar(32) 口味名称
value varchar(255) 口味值

代码开发

一:设计DTO类

java 复制代码
package com.sky.dto;

import com.sky.entity.DishFlavor;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

@Data
public class DishDTO implements Serializable {

    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 List<DishFlavor> flavors = new ArrayList<>();
}

细节:

注意口味用List集合封装,因为前端可以传递多种口味数据(对应口味实体类)

口味实体类如下:

java 复制代码
/**
 * 菜品口味
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DishFlavor implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;
    //菜品id
    private Long dishId;

    //口味名称
    private String name;

    //口味数据list
    private String value;

}

二:Controller层

java 复制代码
package com.sky.controller.admin;

import com.sky.dto.DishDTO;
import com.sky.dto.DishPageQueryDTO;
import com.sky.entity.Dish;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
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.*;
import java.util.List;
import java.util.Set;

/**
 * 菜品管理
 */
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {

    @Autowired
    private DishService dishService;

    /**
     * 新增菜品
     *
     * @param dishDTO
     * @return
     */
    @PostMapping
    @ApiOperation("新增菜品")
    public Result save(@RequestBody DishDTO dishDTO) {
        log.info("新增菜品:{}", dishDTO);
        dishService.saveWithFlavor(dishDTO);//后绪步骤开发
        return Result.success();
    }
}

细节:

注意接口文档上显示返回Result中data是非必须的,所以Result后可以不加泛型。

三:Service类(事务、forEach循环)

java 复制代码
package com.sky.service.impl;


@Service
@Slf4j
public class DishServiceImpl implements DishService {

    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private DishFlavorMapper dishFlavorMapper;

    /**
     * 新增菜品和对应的口味
     *
     * @param dishDTO
     */
    @Transactional
    public void saveWithFlavor(DishDTO dishDTO) {

        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);

        //向菜品表插入1条数据
        dishMapper.insert(dish);//后绪步骤实现

        //获取insert语句生成的主键值
        Long dishId = dish.getId();

        List<DishFlavor> flavors = dishDTO.getFlavors();
        if (flavors != null && flavors.size() > 0) {
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dishId);
            });
            //向口味表插入n条数据
            dishFlavorMapper.insertBatch(flavors);//后绪步骤实现
        }
    }

}

细节:

  • Service层别忘记要把DTO转换为实体对象
    • 利用BeanUtils提供的copyProperties方法可以快速转换
  • 因为新增菜品需要操作两张数据表 (菜品表和口味表),所以需要原子操作,于是定义事务
    • @Transactional注解
  • 又因为口味表需要菜品表的主键 (菜品id),而菜品id是数据库负责维护的自增主键,所以需要mybatis配合进行主键回填
    • Long dishId = dish.getId();成功的前提是mapper层set了id
  • 因为口味是非必须的请求数据,所以首先需要判断集合是否为空
    • 之后需要为口味对象赋值菜品id
    • 最后批量的插入口味表
  • forEach方法的使用
    • 集合.forEach(元素名->{元素所进行的操作})

四:Mapper层(主键回填、批量删除)

dishMapper:

java 复制代码
	/**
     * 插入菜品数据
     *
     * @param dish
     */
    @AutoFill(value = OperationType.INSERT)
    void insert(Dish dish);

对应的xml文件

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.DishMapper">

    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        insert into dish (name, category_id, price, image, description, create_time, update_time, create_user, update_user,status)
        VALUES (#{name},#{categoryId},#{price},#{image},#{description},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})
    </insert>
</mapper>

细节:

这行定义了主键回填:<insert id="insert" useGeneratedKeys="true" keyProperty="id">,会将id的值返回到参数列表的Dish对象dish的id属性中

dishFlavorMapper:

java 复制代码
package com.sky.mapper;

import com.sky.entity.DishFlavor;
import java.util.List;

@Mapper
public interface DishFlavorMapper {
    /**
     * 批量插入口味数据
     * @param flavors
     */
    void insertBatch(List<DishFlavor> flavors);

}

对应的XML:

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.DishFlavorMapper">
    <insert id="insertBatch">
        insert into dish_flavor (dish_id, name, value) VALUES
        <foreach collection="flavors" item="df" separator=",">
            (#{df.dishId},#{df.name},#{df.value})
        </foreach>
    </insert>
</mapper>

细节:

批量插入

<foreach collection="参数列表中传递过来的集合名" item="元素名称(自定义)" separator="(下列元素的属性之间分割的分隔符)">
(#{df.dishId},#{df.name},#{df.value})
</foreach>

形成的效果

( , , ,)

相关推荐
书中自有妍如玉3 分钟前
.net 使用MQTT订阅消息
java·前端·.net
风铃儿~29 分钟前
Spring AI 入门:Java 开发者的生成式 AI 实践之路
java·人工智能·spring
斯普信专业组34 分钟前
Tomcat全方位监控实施方案指南
java·tomcat
忆雾屿1 小时前
云原生时代 Kafka 深度实践:06原理剖析与源码解读
java·后端·云原生·kafka
武昌库里写JAVA1 小时前
iview Switch Tabs TabPane 使用提示Maximum call stack size exceeded堆栈溢出
java·开发语言·spring boot·学习·课程设计
gaoliheng0061 小时前
Redis看门狗机制
java·数据库·redis
我是唐青枫1 小时前
.NET AOT 详解
java·服务器·.net
小白杨树树1 小时前
【WebSocket】SpringBoot项目中使用WebSocket
spring boot·websocket·网络协议
Su米苏2 小时前
Axios请求超时重发机制
java
Undoom2 小时前
🔥支付宝百宝箱新体验!途韵归旅小帮手,让高铁归途变旅行
后端