多表插入
场景:当添加一个菜品时,还需要记录菜品的口味信息,因此需要对菜品表(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 {
@Autowired
private 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 {
@Autowired
private DishMapper dishMapper;
@Autowired
private DishFlavorMapper dishFlavorMapper;
/**
* 新增菜品和对应的口味
* @param dishDTO
*/
@Transactional // 操作多张表的时候,添加@Transactional注解,保证该事件是原子性的,要么全成功,要么全失败
@Override
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,为每个口味附上相关的dishId
flavors.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 // 保证事务的原子性
@Override
public 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>