苍穹外卖学习记录(二)

news2025/2/7 20:53:19

本节,主要是学习业务逻辑,我们以菜品管理为例:
在实现这部分前,我们要完成Mybatis的配置,即指定映射的mapper.xml文件路径以及对应的实体类,这部分配置是在application.yml文件中实现的。

mybatis:
  #mapper配置文件
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.sky.entity
  configuration:
    #开启驼峰命名
    map-underscore-to-camel-case: true

这样配置后,在mapper.xml中如果不指定的话,接收的实体类就是com.sky.entity中的,也就不需要每次都给指定接收类型了。
如下,我们没有指定传入的数据类型,因为在mapper接口中指定了,就可以省略。

 <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>

业务规则:

  • 菜品名称必须是唯一的
  • 菜品必须属于某个分类下,不能单独存在
  • 新增菜品时可以根据情况选择菜品的口味
  • 每个菜品必须对应一张图片

在这里插入图片描述
在这里插入图片描述

文件上传功能实现

我们需要用到的是阿里云对象存储服务:

阿里云OSS简介

​ 阿里云对象存储服务(Object Storage Service,简称OSS)为您提供基于网络的数据存取服务。使用OSS,您可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种非结构化数据文件。
阿里云OSS将数据文件以对象(object)的形式上传到存储空间(bucket)中。

在这里插入图片描述
您可以进行以下操作:

  • 创建一个或者多个存储空间,向每个存储空间中添加一个或多个文件。
  • 通过获取已上传文件的地址进行文件的分享和下载。
  • 通过修改存储空间或文件的属性或元信息来设置相应的访问权限。
  • 在阿里云管理控制台执行基本和高级OSS任务。
  • 使用阿里云开发工具包或直接在应用程序中进行RESTful API调用执行基本和高级OSS任务

阿里云OSS开通与使用

那么我们具体该如何使用呢?进入阿里云官网:https://www.aliyun.com/
在这里插入图片描述
我们点击开通,这个是需要付费的。
在这里插入图片描述

开通成功:

在这里插入图片描述

进入控制台后,我们创建一个存储空间,即创建Bucket,这个Bucket的名称是唯一的。

在这里插入图片描述

文件配置

首先是引入依赖:

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.15.1</version>
</dependency>

随后在application-dev.yml中进行配置,可以看到原本的配置为:

sky:
  alioss:
    endpoint: oss-cn-hangzhou.aliyuncs.com
    access-key-id: LTAI5tPeFLzsPPT8gG3LPW64
    access-key-secret: U6k1brOZ8gaOIXv3nXbulGTUzy6Pd7
    bucket-name: sky-take-out

我们把自己的写进去即可,这个是收费的,因此不要暴露

在这里插入图片描述

随后我们创建AccessKey,并把值复制后我们的配置文件中。

在这里插入图片描述

至于其他的两个参数:

  1. endpoint:你选择杭州就是oss-cn-hangzhou.aliyuncs.com
    你选择北京就是oss-cn-beijing.aliyuncs.com,可以理解吧
  2. bucket-name就是创建的名字(直接点击一下就可以复制啦)

随后在配置文件中读取:

spring:
  profiles:
    active: dev    #设置环境
sky:
  alioss:
    endpoint: ${sky.alioss.endpoint}
    access-key-id: ${sky.alioss.access-key-id}
    access-key-secret: ${sky.alioss.access-key-secret}
    bucket-name: ${sky.alioss.bucket-name}

sky-common模块的properties包中的 AliOssProperties 中,定义配置实体类:

/**
 * 阿里云配置实体类
 */
@Component
/*请注意,为了使用@ConfigurationProperties注解,你需要在Spring Boot应用程序中启用配置绑定功能。
你可以通过在主应用程序类上添加@EnableConfigurationProperties注解来实现。*/
@ConfigurationProperties(prefix = "sky.alioss")
@Data
public class AliOssProperties {

    private String endpoint; //表示OSS服务的访问域名。
    private String accessKeyId; //表示访问OSS服务所需的Access Key ID。
    private String accessKeySecret; //表示访问OSS服务所需的Access Key Secret。
    private String bucketName; //表示要操作的存储桶名称。

生成OSS工具类对象
在sky-server模块的config包中进行配置,读取上面的配置信息,并返回一个操作对象。

/**
 * 配置类,用于创建AliOssUtil对象
 */
@Configuration
@Slf4j
public class OssConfiguration {
    /*在这个配置类中,定义了一个名为aliOssUtil的@Bean方法,用于创建一个AliOssUtil对象。*/
    @Bean
    /*@ConditionalOnMissingBean注解表示当不存在名为aliOssUtil的bean时,才会创建该bean。
      这意味着如果已经有其他地方定义了名为aliOssUtil的bean,那么这个方法将不会执行。*/
    @ConditionalOnMissingBean //保证整个Spring容器中只有一个这个工具类
    /*在方法体中,通过依赖注入的方式获取AliOssProperties对象,并使用它的属性值来创建AliOssUtil对象。*/
    public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){
        log.info("开始创建阿里云文件上传工具类对象:{}",aliOssProperties);
        return new AliOssUtil(aliOssProperties.getEndpoint(),
                aliOssProperties.getAccessKeyId(),
                aliOssProperties.getAccessKeySecret(),
                aliOssProperties.getBucketName());
    }
}

其中,AliOssUtil类定义在sky-commonutils工具包中,改工具类将图像上传到阿里云OSS后会生成一个访问地址,这个地址就是可以直接访问。

/**
 * AliOssUtil类是一个包含文件上传功能的工具类。
 */
@Data
@AllArgsConstructor
@Slf4j
public class AliOssUtil {

    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;

    /**
     * 文件上传
     *
     * @param bytes
     * @param objectName
     * @return
     */
    public String upload(byte[] bytes, String objectName) {

        // 创建OSSClient实例。
        /*在upload方法中,首先创建了一个OSSClient实例,用于与OSS服务进行交互。*/
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            // 创建PutObject请求。
            /*然后,通过调用ossClient.putObject方法将文件上传到指定的存储桶中。*/
            ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));
            /*在上传过程中,通过捕获OSSException和ClientException来处理可能出现的异常情况,并输出相应的错误信息。*/
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        //文件访问路径规则 https://BucketName.Endpoint/ObjectName
        /*最后,构建文件的访问路径,并使用日志记录上传文件的路径。*/
        StringBuilder stringBuilder = new StringBuilder("https://");
        stringBuilder
                .append(bucketName)
                .append(".")
                .append(endpoint)
                .append("/")
                .append(objectName);//拼接路径返回到数据库里

        log.info("文件上传到:{}", stringBuilder.toString());

        return stringBuilder.toString();
    }
}

至此,阿里云OSS的配置完成了。

最后,我们定义对应文件上传接口

/**
 * 通用接口
 */
@RestController
@RequestMapping("/admin/common")
@Api(tags = "通用接口")
@Slf4j
/*CommonController是一个使用AliOssUtil进行文件上传的控制器类。*/
public class CommonController {

    @Autowired
    private AliOssUtil aliOssUtil;

    /**
     * 文件上传
     *
     * @param file
     * @return
     */
    @PostMapping("/upload")
    @ApiOperation("文件上传")
    public Result<String> upload(MultipartFile file) {
        log.info("文件上传:{}", file);
        try {
            //原始文件名
            /*首先通过file.getOriginalFilename()获取原始文件名*/
            String originalFilename = file.getOriginalFilename();
            //截取原始文件名的后缀   dfdfdf.png
            /*然后通过originalFilename.lastIndexOf(".")获取文件名的后缀。*/
            String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
            //构造新文件名称
            /*使用UUID.randomUUID().toString()生成一个随机的文件名,并将后缀拼接在文件名后面,构造出新的文件名。*/
            String objectName = UUID.randomUUID().toString() + extension;

            //文件的请求路径
            /*然后,调用aliOssUtil.upload方法将文件上传到OSS,并获取文件的请求路径。*/
            String filePath = aliOssUtil.upload(file.getBytes(), objectName);
            /*最后,返回一个Result对象,其中包含上传文件的请求路径。*/
            return Result.success(filePath);
        } catch (IOException e) {
            log.error("文件上传失败:{}", e);
        }

        return Result.error(MessageConstant.UPLOAD_FAILED);
    }
}

随后我们在测试时发生报错:

nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to 127.0.0.1:6379] with root cause

java.net.ConnectException: Connection refused: no further information
	at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method) ~[na:1.8.0_201]

这个其实是由于我们没有启动Redis导致的,我们将Redis配置好后,启动服务即可。

在这里插入图片描述

使用了@Configuration后,在服务启动过程中这些就会被加载执行。即这些类在服务启动过程中便会创建好对象从而方便我们使用。

在这里插入图片描述

菜品添加

完成文件上传功能后,这其实是一个公用接口,无论是菜品添加还是其他的文件上传都可以使用,随后,开始菜品添加模块的设计:
分析数据传输的组成,其有一个口味属性,这个属性中还包含许多属性:

在这里插入图片描述

因此,需要封装一个专门用于传递菜品数据的类:

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<>();
}

随后定义Controller的添加菜品接口

	@PostMapping()
    @ApiOperation("新增菜品")
    public Result save(@RequestBody DishDTO dishDTO) {
        log.info("新增菜品:{}", dishDTO);
        dishService.saveWithFlavor(dishDTO);

//        清理缓存数据
        String key = "dish_" + dishDTO.getCategoryId();
        clearCache(key);

        return Result.success();
    }

定义Service的操作,这里涉及到两个表,分别是菜品表与口味表,因此需要开启事务管理,同时由于口味可以有多个,因此可以使用SQL的动态拼接来插入多条数据:

@Override
    @Transactional//由于涉及两张表,因此需要开启事务,即全成功或者全失败
    public void saveWithFlavor(DishDTO dishDTO) {
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);//Spring提供的属性拷贝

//        向菜品表插入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);
        }
    }

关于上面代码中的forEach,其可以实现遍历赋值。

TreeSetTest t1= new TreeSetTest("李白",19);
TreeSetTest t2= new TreeSetTest("杜甫",18);
Set<TreeSetTest> s=new TreeSet<>((o1,  o2) -> o2.getAge()-o1.getAge());
s.add(t1);
s.add(t2);
s.forEach(ss->ss.setAge(11));//这两个for循环语句作用相同,都能够改变s的值,即修改里面的值
for (TreeSetTest ss:s) {
      ss.setAge(11);
}

编写批量插入的SQL语句,在这段语句中,collection的参数就是口味list,每个对象用item代表,这个随便起,保存与下面的对象一致即可,separator=","是用于在每个 (#{item.dishId},#{item.name},#{item.value})后面加一个逗号表示分割。

<insert id="insertBatch">
        insert into dish_flavor (dish_id, name, value) VALUES
             <foreach collection="flavors" separator="," item="item">
                 (#{item.dishId},#{item.name},#{item.value})
             </foreach>
</insert>

这里还需要注意的是,我们需要获取到菜品的id,而此时我们是在添加菜品,在菜品对象中是没有id的,因此可以使用insert插入语句中的返回值
useGeneratedKeys="true"代表需要主键值, keyProperty="id"表示将主键值赋给菜品对象的id属性

<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>

如业务层中所编写的那样,在执行完insert后就可以从对象中获取到属性值了。

//        向菜品表插入1条数据
dishMapper.insert(dish);
//        获取insert语句生成的主键值
Long dishId = dish.getId();
//使用forEach将口味对象的菜品id赋值
flavors.forEach(dishFlavor -> dishFlavor.setDishId(dishId));

菜品分页查询

根据文档分析,菜品查询所接收的参数为:
在这里插入图片描述
因此定义DishPageQueryDTO

package com.sky.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class DishPageQueryDTO implements Serializable {
    private int page;
    private int pageSize;
    private String name;
    //分类id
    private Integer categoryId;
    //状态 0表示禁用 1表示启用
    private Integer status;
}

此外我们看到上面的实体类后面实现了一个Serializable接口,这样做的意义是什么呢?
为何要implements Serializable ?

Controller层定义:

	@GetMapping("/page")
    @ApiOperation("菜品分页查询")
    public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO) {
        log.info("菜品分页查询:{}", dishPageQueryDTO);
        PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);//后绪步骤定义
        return Result.success(pageResult);
    }

基于PageHelper实现分页查询的业务层代码:

	@Override
    public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
        PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());
        Page<DishVO> page=dishMapper.pageQuery(dishPageQueryDTO);
        return new PageResult(page.getTotal(), page.getResult());
    }

这里需要注意的是返回结果是DishVO,因为结果涉及两个表的联合查询,要查类别名称。

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

至此,完成菜品的分页查询功能。

菜品删除

业务规则:

  • 可以一次删除一个菜品,也可以批量删除菜品
  • 起售中的菜品不能删除
  • 被套餐关联的菜品不能删除
  • 删除菜品后,关联的口味数据也需要删除掉

在这里插入图片描述

根据上面的业务逻辑,这将涉及三个表:菜品表,口味表,套餐与菜品关系表

在这里插入图片描述
Controller层定义,前端发来的请求是?参数名=参数值的形式,ids原本为字符串类型,即1,3,5,7,8,我们希望将这种数据转换为List列表这种形式,我们可以自己处理,但SpringMVC为我们提供了一种方法,即在前面加一个@RequestParam注解即可将其转换为List形式。

 	@DeleteMapping
    @ApiOperation("菜品批量删除")
    public Result delete(@RequestParam List<Long> ids) {
        log.info("菜品批量删除:{}", ids);
        dishService.deleteBatch(ids);

//      将所有的菜品缓存数据清理掉,所有以dish_开头的key
        clearCache("dish_*");
        return Result.success();
    }

在这里插入图片描述

随后Service层定义业务逻辑,要求删除菜品时需检查在套餐中是否含有,还要检查是否在售,在删除时要连同菜品口味一并删除。

@Override
    @Transactional
    public void deleteBatch(List<Long> ids) {
//        判断当前菜品是否能够删除---是否存在起售中的菜品??
        ids.forEach(id->{
            Dish dish = dishMapper.getById(id);
            if (dish.getStatus() == StatusConstant.ENABLE) {
//                当前菜品处于起售中,不能删除
                throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
            }
        });

//        判断当前菜品是否能够删除---是否被套餐关联了??
        List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(ids);
        if (setmealIds != null && setmealIds.size() > 0) {
//            当前菜品被套餐关联了,不能删除
            throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
        }

//        删除菜品表中的菜品数据
        ids.forEach(id->{
            dishMapper.deleteById(id);

//            删除菜单关联的口味数据
            dishFlavorMapper.deleteByDishId(id);
        });

    }

如果被套餐关联或者在售卖中,则会抛出异常,抛出异常后这个异常就会被捕获,因为这些异常都是BaseException的子类,因此都可以在全局异常处理中被捕获:

package com.sky.exception;
public class DeletionNotAllowedException extends BaseException {
    public DeletionNotAllowedException(String msg) {
        super(msg);
    }
}
 	/**
 * 全局异常处理器,处理项目中抛出的业务异常
 */
@RestControllerAdvice//开启异常捕获
@Slf4j
public class GlobalExceptionHandler {
    /**
     * 捕获业务异常
     * @param ex
     * @return
     */
    @ExceptionHandler
    public Result exceptionHandler(BaseException ex){
        log.error("异常信息:{}", ex.getMessage());
        return Result.error(ex.getMessage());
    }
}

查看菜品是否与套餐相关联,原始的SQL语句

select setmeal_id form setmeal_dish where dish_id in (1,3,5,8)

Mybatis中的动态SQL:


<select id="getSetmealIdsByDishIds" resultType="java.lang.Long">
        select setmeal_id
        from setmeal_dish where dish_id in
        <foreach collection="ids" separator="," item="item" open="(" close=")">
            #{item}
        </foreach>
    </select>

至此,菜品删除便完成了,但我们发现一个问题,在删除菜品时,如果id过多,则会执行多次删除语句,这样势必对性能会造成影响,那么我们可以考虑使用批量删除语句:

delete from tables where id in (1,3,5,6,7)
//        删除菜品表中的菜品数据
//        ids.forEach(id->{
//            dishMapper.deleteById(id);
//
            删除菜单关联的口味数据
//            dishFlavorMapper.deleteByDishId(id);
//        });
        //批量删除菜品
        dishMapper.deleteByIds(ids);
        dishFlavorMapper.deleteByDishIds(ids);

对应的动态删除xml配置:

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

菜品修改

菜品修改涉及到的接口有:

  • 根据id查询菜品
  • 上传文件
  • 查询菜品类别
  • 修改菜品

其中,上传文件与查询菜品类别我们已经完成了,接下来便是根据id查询菜品和修改菜品了。

首先定义要返回的数据类型:

package com.sky.vo;

import com.sky.entity.DishFlavor;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DishVO 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 LocalDateTime updateTime;
    //分类名称
    private String categoryName;
    //菜品关联的口味
    private List<DishFlavor> flavors = new ArrayList<>();

    //private Integer copies;
}

Controller层接口实现:

@GetMapping("/{id}")
@ApiOperation("根据id查询菜品")
public Result<DishVO> getById(@PathVariable Long id) {
    log.info("根据id查询菜品:{}", id);
    DishVO dishVO = dishService.getByIdWithFlavor(id);
    return Result.success(dishVO);
}

业务层代码

@Override
    public DishVO getByIdWithFlavor(Long id) {
//        根据id查询菜品数据
        Dish dish = dishMapper.getById(id);

//        根据菜品id查询口味数据
        List<DishFlavor> dishFlavorList = dishFlavorMapper.getByDishId(id);

//        将查询到的数据封装到vo
        DishVO dishVO = new DishVO();
        BeanUtils.copyProperties(dish, dishVO);
        dishVO.setFlavors(dishFlavorList);

        return dishVO;
    }

Mybatis 对应的SQL语句:

<select id="getById" resultType="com.sky.entity.Dish">
        select *
        from dish where id=#{id};
    </select>

随后开始修改操作的接口开发,该修改涉及菜品表与口味表:

@PutMapping
    @ApiOperation("修改菜品")
    public Result update(@RequestBody DishDTO dishDTO) {
        log.info("修改菜品:{}", dishDTO);
        dishService.updateWithFlavor(dishDTO);

        //将所有的菜品缓存数据清理掉,所有以dish_开头的key
        clearCache("dish_*");

        return Result.success();
    }

由于涉及口味的修改,而我们可能会对口味进行增删操作,这还要判断,因此可以进行先全部删除,再将现有口味重新添加的方式来实现,即逻辑上是修改,技术实现上是删除与添加。

@Override
    public void updateWithFlavor(DishDTO dishDTO) {
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);

//        修改菜品基本信息
        dishMapper.update(dish);

//        删除原有的口味信息
        dishFlavorMapper.deleteByDishId(dishDTO.getId());

//        重新插入口味数据
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if (flavors != null && flavors.size() > 0) {
            flavors.forEach(dishFlavor -> dishFlavor.setDishId(dishDTO.getId()));

//            向口味表插入n条数据
            dishFlavorMapper.insertBatch(flavors);
        }

菜品修改动态SQL

<update id="update">
        update dish
        <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>

批量删除与插入口味信息

 <delete id="deleteByDishIds">
        delete
        from dish_flavor where dish_id in
        <foreach collection="ids" item="dishid" separator="," open="(" close=")">
            #{dishid}
        </foreach>
    </delete>
<insert id="insertBatch">
        insert into dish_flavor (dish_id, name, value) VALUES
             <foreach collection="flavors" separator="," item="item">
                 (#{item.dishId},#{item.name},#{item.value})
             </foreach>
</insert>

至此,便完成了菜品信息的增删改查了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1598287.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机网络(四)网络层

网络层 基本概念 网络互联&#xff1a; 将两个以上的计算机网络&#xff0c;通过一定的办法&#xff0c;用一种或多种通信处理设备(即中间设备)相互连接起来&#xff0c;以构成更大的网络系统。中间设备又称中间系统或中继系统 中继系统分为4种&#xff1a; 物理层中继系统…

thinkphp 框架封装curl请求

tp6 或者 tp8框架 在框架的app/common.php 文件里加一些方法就可以 app\common.php 在这个文件里加 以下代码 就可以实现基于 curl的请求方法 (记得要开启 php的curl扩展) 查看方法 cmd里输入 php -m if (!function_exists(get)) {/*** 发送get请求* param string $url 请求…

ChatGPT实用指南2024

随着ChatGPT技术的演进&#xff0c;越来越多的人开始在工作中利用此工具。以下是关于ChatGPT的实用指南&#xff0c;适合不太熟悉此技术的朋友参考。 一、ChatGPT概述 1. ChatGPT是什么&#xff1f; ChatGPT是基于OpenAI开发的GPT大型语言模型的智能对话工具。它能够通过自然语…

前端框架模板

前端框架模板 1、vue-element-admin vue-element-admin是基于element-ui 的一套后台管理系统集成方案。 **功能&#xff1a;**https://panjiachen.github.io/vue-element-admin-site/zh/guide/#功能 **GitHub地址&#xff1a;**GitHub - PanJiaChen/vue-element-admin: :t…

网盘——添加好友

关于添加好友&#xff0c;过程如下&#xff1a; A、首先客户端A发送加好友的请求&#xff0c;发送的信息包括双方的用户名 B、当服务器收到请求之后&#xff0c;服务器将数据库中在线用户查找出来&#xff0c;如果客户端B已经是你的好友了&#xff0c;服务器告诉客户端A他已经…

【深度学习】深度学习md笔记总结第5篇:神经网络与tf.keras,学习目标【附代码文档】

深度学习笔记完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;深度学习课程&#xff0c;深度学习介绍要求,目标,学习目标,1.1.1 区别,学习目标,学习目标。TensorFlow介绍&#xff0c;2.4 张量学习目标,2.4.1 张量(Tensor),2.4.2 创建张量的指令,2.4.3 张量…

Sublime Text下载,安装,安装插件管理器,下载汉化插件

SublimeTest官网 © Sublime Text中文网 下载安装 一路点击安装即可 安装插件管理器 管理器官网安装 - 包控制 (packagecontrol.io) 手动安装将3 位置点击网址下载 再打开SublimeTest 点击 选择第一个Browse Packages..... 将会跳转到文件夹中 进入上一个文件夹 在进入…

【模拟】Leetcode 提莫攻击

题目讲解 495. 提莫攻击 算法讲解 前后的两个数字之间的关系要么是相减之差 > 中毒时间 &#xff0c;要么反之 那即可通过示例&#xff0c;进行算法的模拟&#xff0c;得出上图的计算公式 class Solution { public:int findPoisonedDuration(vector<int>& time…

MySql 安装,小白也可以学会成功安装的保姆级教程

MySql 安装 文章目录 MySql 安装1.Mysql下载1.1 访问下载链接1.2 选择合适版本1.3 下载安装包 2.MySql安装3.安装成功检测验证3.1 mysql自带控制台验证3.2 win系统控制台进入验证 4. mysql 配置path5. navicat 连接 mysql 1.Mysql下载 1.1 访问下载链接 MySQL Downloads 这里…

定时器、PWM定时器、UART串口通信

我要成为嵌入式高手之4月15日ARM第八天&#xff01;&#xff01; ———————————————————————————— 定时器 S3C2440A 有 5 个 16 位定时器。其中定时器 0、1、2 和 3 具有脉宽调制&#xff08;PWM&#xff09;功能。定时器 4 是一个无 输出引脚的内部…

电商技术揭秘二十五:电商平台的智能库存管理与优化

相关系列文章 电商技术揭秘一&#xff1a;电商架构设计与核心技术 电商技术揭秘二&#xff1a;电商平台推荐系统的实现与优化 电商技术揭秘三&#xff1a;电商平台的支付与结算系统 电商技术揭秘四&#xff1a;电商平台的物流管理系统 电商技术揭秘五&#xff1a;电商平台…

【DM8】ET SQL性能分析工具

通过统计SQL每个操作符的时间花费&#xff0c;从而定位到有性能问题的操作&#xff0c;指导用户去优化。 开启ET工具 INI参数&#xff1a; ENABLE_MONITOR1 MONITOR_SQL_EXEC1 查看参数 select * FROM v$dm_ini WHERE PARA_NAMEMONITOR_SQL_EXEC;SELECT * FROM v$dm_ini WH…

Fiddler工具的操作和功能时-----定位到步骤图(助力抓包)

前言&#xff1a; 继续上一篇&#xff0c;已经对fiddler的安装、配置和代理的问题进行了讲解&#xff1a; Fiddle配置代理&#xff0c;保手机模拟器访问外部网络-CSDN博客 本章&#xff0c;讲对一些fiddler的操作进行一系列讲解&#xff01;Fiddler作为一款网络调试工具&…

小型时间继电器ST3PA-C DC24V 带插座PF085A 导轨安装 JOSEF约瑟

ST3P系列时间继电器 系列型号 ST3PF-2Z(JSZ3F-2Z) 5s AC110V ST3PF(JSZ3F) 10s AC48V ST3PC-1(AH3-3) 5s DC24V ST3PC-1(AH3-3) 2h AC220V ST3PC-F(JSZ3C-F) AC380V ST3PA-E(JSZ3A-E) DC24V ST3PA-F(JSZ3A-F) DC24V ST3PF(JSZ3F) 10s AC36V ST3PC-1(AH3-3) 10s AC24V ST3PC-1…

基于SSM+Jsp+Mysql的贝儿米幼儿教育管理系统

开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包…

【包邮送书】MicroPython项目开发实战

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…

读天才与算法:人脑与AI的数学思维笔记01_洛夫莱斯测试

1. 创造力 1.1. 创造力是一种原动力&#xff0c;它驱使人们产生新的、令人惊讶的、有价值的想法&#xff0c;并积极地将这些想法付诸实践 1.2. 创造出在表面上看似新的东西相对容易 1.3. 在遇到偶然间的创造性行为时&#xff0c;都会表现得异…

建立时间/保持时间为负是什么情况

目录 建立时间为负保持时间为负参考 在说明建立时间和保持时间为何为负的情况下&#xff0c;首先可以看看建立时间Tsu和保持时间Th的由来&#xff0c;可参考如下两篇文章&#xff1a; 建立时间和保持时间理解_为什么要满足建立时间和保持时间-CSDN博客 ic基础|时序篇&#xff…

分布式文件系统HDFS-1

文章目录 主要内容一.分布式文件系统HDFS1.定义2.优缺点3.原理4.设计思想及原则5.框架结构 6.HDFS的主要组件包括7.块、名称节点和数据节点8.HDFS冗余数据保存9.数据存取策略 总结 主要内容 HDFS 一.分布式文件系统HDFS 1.定义 HDFS&#xff08;Hadoop Distributed File Syst…

使用python在本地指定的目录临时模拟服务器(3),2024年最新网易 面经

先自我介绍一下&#xff0c;小编浙江大学毕业&#xff0c;去过华为、字节跳动等大厂&#xff0c;目前阿里P7 深知大多数程序员&#xff0c;想要提升技能&#xff0c;往往是自己摸索成长&#xff0c;但自己不成体系的自学效果低效又漫长&#xff0c;而且极易碰到天花板技术停滞…