多表插入
场景:当添加一个菜品时,还需要记录菜品的口味信息,因此需要对菜品表(dish)和口味表(dish_flavor)同时进行插入操作。
两个表的字段:
代码思路:由DishController将前端的请求派发给相应的业务层(DishService),业务层随后通过调用持久层(DishMapper,DishFlavorMapper)进行数据的增删改。
细节:
- 由于要操作两张表,所以需要在业务层的对应方法上添加@Transactional,保证该事务的原子性,见DishServiceImpl;
- 插入口味数据时,需要获取刚插入的dish_id值,通过设置useGeneratedKeys实现,见DishMapper.xml;
代码(由上至下):
控制层:
DishController
package com.sky.controller.admin;import com.sky.dto.DishDTO;
import com.sky.result.Result;
import com.sky.service.DishService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;/*** ClassName: DishController* PackageName: com.sky.controller.admin* Description: 菜品管理** @Author Xiyan Zhong* @Create 2023/12/20 下午2:19* @Version 1.0*/@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {@Autowiredprivate DishService dishService;/*** 新增菜品* @param dishDTO* @return*/@PostMapping@ApiOperation("新增菜品")public Result save(@RequestBody DishDTO dishDTO){log.info("新增菜品:{}",dishDTO);dishService.saveWithFlavor(dishDTO);return Result.success();}}
业务层:
DishService:
package com.sky.service;import com.sky.dto.DishDTO;/*** ClassName: DishService* PackageName: com.sky.service* Description:** @Author Xiyan Zhong* @Create 2023/12/20 下午2:23* @Version 1.0*/public interface DishService {/*** 新增菜品和对应的口味* @param dishDTO*/public void saveWithFlavor(DishDTO dishDTO);
}
DishServiceImpl:
package com.sky.service.impl;import com.sky.dto.DishDTO;
import com.sky.entity.Dish;
import com.sky.entity.DishFlavor;
import com.sky.mapper.DishFlavorMapper;
import com.sky.mapper.DishMapper;
import com.sky.service.DishService;
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;/*** ClassName: DishServiceImpl* PackageName: com.sky.service.impl* Description:** @Author Xiyan Zhong* @Create 2023/12/20 下午2:25* @Version 1.0*/@Service
@Slf4j
public class DishServiceImpl implements DishService {@Autowiredprivate DishMapper dishMapper;@Autowiredprivate DishFlavorMapper dishFlavorMapper;/*** 新增菜品和对应的口味* @param dishDTO*/@Transactional // 操作多张表的时候,添加@Transactional注解,保证该事件是原子性的,要么全成功,要么全失败@Overridepublic 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,为每个口味附上相关的dishIdflavors.forEach(dishFlavor -> {dishFlavor.setDishId(dishId);});// 向口味表插入n条数据dishFlavorMapper.insertBatch(flavors);}}
}
需要注意的是,为了保证@Transactional有效,需要在当前项目的入口类添加@EnableTransactionManagement,启动注解方式的事务管理,如:
package com.sky;import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
public class SkyApplication {public static void main(String[] args) {SpringApplication.run(SkyApplication.class, args);log.info("server started");}
}
持久层:
DishMapper 接口
package com.sky.mapper;import com.sky.annotation.AutoFill;
import com.sky.entity.Dish;
import com.sky.enumeration.OperationType;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;/*** ClassName: DishMapper* PackageName: com.sky.mapper* Description:** @Author Xiyan Zhong* @Create 2023/12/15 下午2:52* @Version 1.0*/@Mapper
public interface DishMapper {/*** 根据分类id查询菜品数量* @param categoryId* @return*/@Select("select count(id) from dish where category_id = ${categoryId}")Integer countByCategoryId(Long categoryId);/*** 插入菜品数据* @param dish*/@AutoFill(value = OperationType.INSERT)void insert(Dish dish);
}
DishMapper.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"><!--设置useGeneratedKeys="true" 表示需要获得插入数据后生成的主键值,返回到keyProperty="id",即Dish对象中的id属性--><insert id="insert" parameterType="Dish" 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>
DishFlavorMapper 接口
package com.sky.mapper;import com.sky.entity.DishFlavor;
import org.apache.ibatis.annotations.Mapper;import java.util.List;/*** ClassName: DishFlavorMapper* PackageName: com.sky.mapper* Description:** @Author Xiyan Zhong* @Create 2023/12/20 下午2:42* @Version 1.0*/
@Mapper
public interface DishFlavorMapper {/*** 批量插入口味数据* @param flavors*/void insertBatch(List<DishFlavor> flavors);
}
DishFlavorMapper.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" parameterType="DishFlavor">insert into dish_flavor (dish_id, name, value)values<foreach collection="flavors" item="df" separator=",">(#{df.dishId},#{df.name},#{df.value})</foreach></insert>
</mapper>
多表删除
场景:当删除一个菜品时,不仅要删除菜品表中的数据,同时也要删除口味表中对应数据和菜品与分类表中的数据。
相关表的关系:
代码思路:由DishController将前端的请求派发给相应的业务层(DishService),业务层随后通过调用持久层(DishMapper,DishFlavorMapper,SetmealDishMapper)进行数据的增删改。
细节:
- 由于要操作三张表,同样需要在业务层的对应方法上添加@Transactional,保证该事务的原子性,见DishServiceImpl;
- 删除菜品数据时,需要完成以下业务操作:
- 判断当前菜品是否能够删除——是否存在起售中
- 判断当前菜品是否能够删除——是否被套餐关联
- 删除菜品表中的菜品数据
- 删除菜品关联的口味数据
代码(由上至下):
DishController
@DeleteMapping@ApiOperation("菜品删除")// 通过@RequestParam解析前端请求地址中字符串参数ids=1,2,3...为一个列表public Result delete(@RequestParam List<Long> ids){log.info("菜品批量删除:{}",ids);dishService.deleteBatch(ids);return Result.success();}
DishService
/*** 菜品的批量删除* @param ids*/void deleteBatch(List<Long> ids);
DishServiceImpl
/*** 菜品的批量删除** @param ids*/@Transactional // 保证事务的原子性@Overridepublic void deleteBatch(List<Long> ids) {// 判断当前菜品是否能够删除——是否存在起售中for (Long id : ids) {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);}// 删除菜品表中的菜品数据/*for (Long id : ids) {dishMapper.deleteById(id);// 删除菜品关联的口味数据dishFlavorMapper.deleteByDishId(id);}*/// sql: delete from dish where id in (?,?,?)// 根据菜品id集合批量删除菜品数据dishMapper.deleteByIds(ids);// 根据菜品id集合批量删除口味数据dishFlavorMapper.deleteByDishIds(ids);}
SetmealDishMapper提供查询操作
SetmealDishMapper
package com.sky.mapper;import org.apache.ibatis.annotations.Mapper;import java.util.List;/*** ClassName: SetmealDishMapper* PackageName: com.sky.mapper* Description:** @Author Xiyan Zhong* @Create 2023/12/20 下午7:29* @Version 1.0*/@Mapper
public interface SetmealDishMapper {/*** 根据菜品id查询对应的套餐id* @param dishIds* @return*/List<Long> getSetmealIdsByDishIds(List<Long> dishIds);}
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"><!--对应sql语句:select setmeal_id from setmeal_dish where dish_id in (?,?,?)--><select id="getSetmealIdsByDishIds" resultType="java.lang.Long">select setmeal_id from setmeal_dish where dish_id in<foreach collection="dishIds" item="dishId" separator="," open="(" close=")">#{dishId}</foreach></select>
</mapper>
其中分别实现了单个、批量删除操作
DishMapper
/*** 根据主键删除菜品数据* @param id*/@Delete("delete from dish where id = #{id}")void deleteById(Long id);/*** 根据菜品id集合批量删除菜品* @param ids*/void deleteByIds(List<Long> ids);
DishMapper.xml
<delete id="deleteByIds">delete from dish where id in<foreach collection="ids" open="(" close=")" separator="," item="id">#{id}</foreach></delete>
DishFlavorMapper
/*** 根据菜品id删除对应的口味数据* @param dishId*/@Delete("delete from dish_flavor where dish_id = #{dishId}")void deleteByDishId(Long dishId);/*** 据菜品id批量删除对应的口味数据* @param dishIds*/void deleteByDishIds(List<Long> dishIds);
DishFlavorMapper.xml
<delete id="deleteByDishIds">delete from dish_flavor where dish_id in<foreach collection="dishIds" open="(" close=")" separator="," item="dishId">#{dishId}</foreach></delete>