效果预览
新增菜品
需求分析
查看产品原型分析需求, 包括用到哪些接口, 业务的限制规则
业务规则
- 菜品名称必须是唯一的
- 菜品必须属于某个分类下, 不能单独存在
- 新增菜品时可以根据情况选择菜品的口味
- 每个菜品必须对应一张图片
接口设计
根据类型查询分类接口
文件上传接口
新增菜品接口
数据表设计
设计dish菜品表 和 dish_flavor口味表
分类查询接口
在分类管理模块中已完成
文件上传接口
定义文件上传的Controler
/**
* 通用接口
*/
@RestController
@RequestMapping("/admin/common")
@Api(tags = "通用接口")
@Slf4j
public class CommonController {
/**
* 文件上传
*
* @param file
* @return
*/
@PostMapping("/upload")
@ApiOperation("文件上传")
public Result<String> update(MultipartFile file) {
log.info("文件上传:{}", file);
return null;
}
}
配置阿里云OSS参数: 通常把application.yml作为主配置文件, 根据运行环境引用其他配置文件的值
// 配置属性类
@Component
@ConfigurationProperties(prefix = "sky.alioss")
@Data
public class AliOssProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
}
-
使用 @ConfigurationProperties(prefix = "sky.alioss")注解 定义配置属性类
-
配置属性类的作用就是读取配置文件中的属性, 把属性的值封装到对象中
-
配置文件中用横线分隔, 配置属性类使用驼峰命名法
-
springboot框架可以自动进行驼峰命名映射
spring:
profiles:
active: devsky:
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} -
在主配置文件中配置OSS连接信息
-
程序运行时动态引用环境配置文件中的值
-
active: dev 该属性指定引用开发环境的信息
-
使用 active 属性可以实现代码环境的快捷切换
sky:
alioss:
endpoint: oss-cn-beijing.aliyuncs.com
access-key-id: ....
access-key-secret: ....
bucket-name: sky-itcast-cangqiongwaimai -
在开发环境配置文件中配置具体的oss连接参数
准备工具类: 已经提供了用于文件上传的工具类
@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实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
// 创建PutObject请求。
ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));
} 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();
}
}
创建配置类, 用于创建AliOssUtil对象
@Configuration // 声明配置类
@Slf4j
public class OssConfiguration {
@Bean // 程序启动时创建阿里云OSS对象并交给IOC容器管理
@ConditionalOnMissingBean //条件装配注解, 指定该对象为单例模式, 只创建一个
public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties) {
// 通过参数注入的形式注入aliOssProperties对象
// 通过该对象可以拿到属性配置类封装好的的属性
log.info("开始创建阿里云文件上传工具类对象: {}", aliOssProperties);
return new AliOssUtil(aliOssProperties.getEndpoint(),
aliOssProperties.getAccessKeyId(),
aliOssProperties.getAccessKeySecret(),
aliOssProperties.getBucketName());
}
}
使用aliOssUtil对象, 完成文件上传功能, 为了防止文件重名覆盖, 需要构建新的文件名
/**
* 通用接口
*/
@RestController
@RequestMapping("/admin/common")
@Api(tags = "通用接口")
@Slf4j
public class CommonController {
@Autowired
private AliOssUtil aliOssUtil;
/**
* 文件上传
*
* @param file
* @return
*/
@PostMapping("/upload")
@ApiOperation("文件上传")
public Result<String> update(MultipartFile file) {
log.info("文件上传:{}", file);
try {
// 原始文件名
String originalFilename = file.getOriginalFilename();
// 截取原始文件名的后缀(xxx.png)
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
// 构建新文件名
String objectName = UUID.randomUUID().toString() + extension;
// 文件上传
String filePath = aliOssUtil.upload(file.getBytes(), objectName);
return Result.success(filePath);
} catch (IOException e) {
log.info("文件上传失败:{}", e);
}
return Result.error(MessageConstant.UPLOAD_FAILED);
}
}
新增菜品接口
准备DishDTO用来封装前端传递的菜品数据
@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<>();
}
准备DishFlavor实体类, 用来封装菜品的口味数据
/**
* 菜品口味
*/
@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;
}
新建DishController
/**
* 菜品管理
*/
@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接口 以及 DishServiceImpl实现类
public interface DishService {
/**
* 新增菜品和对应的口味
* @param dishDTO
*/
public void saveWithFlavor(DishDTO dishDTO);
}
@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);
// 向口味表插入n条数据
Long dishId = dish.getId(); //拿到菜品id, SQL中已开启
List<DishFlavor> flavors = dishDTO.getFlavors(); //拿到口味数据
if (flavors != null && flavors.size() > 0) {
flavors.forEach(dishFlavor -> {
// 设置口味关联的菜品id
dishFlavor.setDishId(dishId);
});
dishFlavorMapper.insertBatch(flavors);
}
}
}
@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
public class SkyApplication {
public static void main(String[] args) {
SpringApplication.run(SkyApplication.class, args);
log.info("server started");
}
}
- 一个方法中操作多个表, 使用@Transactional注解开启事务管理
- @EnableTransactionManagement注解不是严格必须的, 但是显示的声明能够确保Spring容器能够识别并管理
@Transactional
注解标记的方法 - 使用 BeanUtils.copyProperties(数据源对象, 目标对象) 方法进行对象属性拷贝, 可以轻松的进行对象属性的赋值, 前提是两个对象的属性名完全一致
新建DishMapper操作菜品表
@Mapper
public interface DishMapper {
/**
* 插入菜品
*
* @param dish
*/
@AutoFill(value = OperationType.INSERT) // 公共字段填充
void insert(Dish dish);
}
<?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>
- useGeneratedKeys="true" 开启sql执行完毕返回数据
- keyProperty="id" 指定返回id字段
新建DishFlavorMapper操作菜品口味表
@Mapper
public interface DishFlavorMapper {
/**
* 批量插入口味数据
* @param flavors
*/
void insertBatch( List<DishFlavor> flavors);
}
<?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>
联调测试
录入的数据项比较多, 直接采用前后端联调进行测试
菜品分页查询
分析和设计
查看产品原型, 分析业务规则
- 根据页码展示菜品信息
- 每页展示10条数据
- 分页查询时可以根据需要输入菜品名称, 菜品分类, 菜品状态进行查询
接口设计, 分类名称需要根据菜品id查询分类表得到
代码开发
根据前端请求参数定义对应的DTO
根据接口返回的结果定义对应的VO
Controller
/**
* 菜品管理
*/
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
/**
* 菜品分页查询
*
* @param dishPageQueryDTO
* @return
*/
@GetMapping("/page")
@ApiOperation("菜品分页查询接口")
public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO) {
log.info("菜品分页查询:{}", dishPageQueryDTO);
PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);
return Result.success(pageResult);
}
}
Service
public interface DishService {
/**
* 菜品分页查询
* @param dishPageQueryDTO
* @return
*/
PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO);
}
@Service
@Slf4j
public class DishServiceImpl implements DishService {
@Autowired
private DishMapper dishMapper;
/**
* 菜品分页查询
*
* @param dishPageQueryDTO
* @return
*/
public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
// 开启分页查询
PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());
// 查询业务
Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);
// 返回数据
return new PageResult(page.getTotal(), page.getResult());
}
}
Mapper
@Mapper
public interface DishMapper {
/**
* 菜品分页查询
*
* @param dishPageQueryDTO
* @return
*/
Page<DishVO> pageQuery(DishPageQueryDTO dishPageQueryDTO);
}
<?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">
<select id="pageQuery" resultType="com.sky.vo.DishVO">
select d.*, c.name as categoryName from dish d left outer 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.category_id = #{categoryId}
</if>
<if test="status != null">
and d.status = #{status}
</if>
</where>
order by d.create_time desc
</select>
</mapper>
- 对于比较复杂的查询语句, 可以在SQL图形化工具中, 编写语句并测试, 提高测试效率
功能测试
接口测试
前后端联调
删除菜品
分析和设计
需求分析: 查看产品原型, 确定业务规则
- 可以一次删除一个菜品, 也可以批量删除菜品
- 起售中的菜品不能删除
- 被套餐关联的菜品不能删除
- 删除菜品后, 关联的口味数据也需要删除
接口设计
数据表设计: 删除菜品涉及到三张表的操作, 菜品表, 菜品口味表, 菜品和口味中间表
代码开发
Controller
/**
* 菜品管理
*/
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
/**
* 菜品批量删除
*
* @param ids
* @return
*/
@DeleteMapping
@ApiOperation("菜品批量删除")
public Result delete(@RequestParam List<Long> ids) {
// @RequestParam注解
// 可以自动把query参数中的参数封装到List<long> ids容器中
// "1,2,3,4" --> [1,2,3,4]
log.info("菜品批量删除:{}", ids);
dishService.deleteBath(ids);
//删除缓存数据
cleanCache("dish_*");
return Result.success();
}
}
Service
public interface DishService {
/**
* 批量删除菜品
* @param ids
*/
void deleteBath(List<Long> ids);
}
@Service
@Slf4j
public class DishServiceImpl implements DishService {
@Autowired
private DishMapper dishMapper;
@Autowired
private DishFlavorMapper dishFlavorMapper;
@Autowired
private SetmealDishMapper setmealDishMapper;
/**
* 批量删除菜品
*
* @param ids
*/
@Transactional // 开启事务管理
public void deleteBath(List<Long> ids) {
log.info("根据菜品id删除口味数据:{}", 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);
}
}
}
Mapper
@Mapper
public interface DishMapper {
/**
* 根据id查询菜品
*
* @param id
* @return
*/
@Select("select * from dish where id = #{id}")
Dish getById(Long id);
/**
* 根据id删除菜品数据
*
* @param id
*/
@Delete("delete from dish where id = #{id}")
void deleteById(Long id);
}
@Mapper
public interface SetmealDishMapper {
/**
* 根据菜品id查询套餐id
* @param dishIds
* @return
*/
List<Long> getSetmealIdsByDishIds(List<Long> dishIds);
}
<?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">
<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>
@Mapper
public interface DishFlavorMapper {
/**
* 根据id删除对应的口味数据
* @param dishId
*/
@Delete("delete from dish_flavor where dish_id = #{dishId}")
void deleteByDishId(Long dishId);
}
代码优化
优化前: sql语句的数量不固定, 删除多个菜品就调用多次sql
优化后: sql语句的数量是固定的, 删除多个菜品也是调用两条sql
@Service
@Slf4j
public class DishServiceImpl implements DishService {
@Autowired
private DishMapper dishMapper;
@Autowired
private DishFlavorMapper dishFlavorMapper;
/**
* 批量删除菜品
*
* @param ids
*/
@Transactional
public void deleteBath(List<Long> ids) {
... ...
// 删除菜品表中的数据(优化前)
// for (Long id : ids) {
// 删除菜品数据
// dishMapper.deleteById(id);
// 删除菜品关联的口味数据
// dishFlavorMapper.deleteByDishId(id);
// }
// 优化后
// 批量删除菜品数据
dishMapper.deleteByIds(ids);
// 批量删除菜品关联的口味数据
log.info("根据菜品id删除口味数据:{}", ids);
dishFlavorMapper.deleteByDishIds(ids);
}
}
@Mapper
public interface DishMapper {
/**
* 根据ids批量删除菜品数据
* @param ids
*/
void deleteByIds(List<Long> ids);
}
<?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">
<delete id="deleteByIds">
delete from dish where id in
<foreach collection="ids" close=")" open="(" separator="," item="id">
#{id}
</foreach>
</delete>
</mapper>
@Mapper
public interface DishFlavorMapper {
/**
* 根据菜品ids批量删除口味数据
* @param dishIds
*/
void deleteByDishIds(List<Long> dishIds);
}
<?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">
<delete id="deleteByDishIds">
delete from dish_flavor where dish_id in
<foreach collection="dishIds" item="dishId" separator="," open="(" close=")">
#{dishId}
</foreach>
</delete>
</mapper>
功能测试
修改菜品
分析和设计
查看原型: 完成菜品信息的回显和更新
接口设计
根据id查询菜品
Controller
/**
* 菜品管理
*/
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
/**
* 根据id查询菜品
*
* @param id
* @return
*/
@GetMapping("/{id}")
@ApiOperation("根据id查询菜品")
public Result<DishVO> getById(@PathVariable long id) {
log.info("根据id查询菜品:{}", id);
DishVO dishVO = dishService.getByIdWithFlavor(id);
return Result.success(dishVO);
}
}
Service
public interface DishService {
/**
* 根据id查询菜品
* @param id
* @return
*/
DishVO getByIdWithFlavor(long id);
}
@Service
@Slf4j
public class DishServiceImpl implements DishService {
@Autowired
private DishMapper dishMapper;
@Autowired
private DishFlavorMapper dishFlavorMapper;
/**
* 根据id查询菜品和对应的口味数据
*
* @param id
* @return
*/
public DishVO getByIdWithFlavor(long id) {
// 根据id查询菜品数据
Dish dish = dishMapper.getById(id);
// 根据菜品id查询口味数据
List<DishFlavor> dishFlavors = dishFlavorMapper.getByDishId(id);
// 把查询结果封装到VO
DishVO dishVO = new DishVO();
BeanUtils.copyProperties(dish, dishVO);
dishVO.setFlavors(dishFlavors);
return dishVO;
}
}
Mapper
@Mapper
public interface DishMapper {
/**
* 根据id查询菜品
*
* @param id
* @return
*/
@Select("select * from dish where id = #{id}")
Dish getById(Long id);
}
@Mapper
public interface DishFlavorMapper {
/**
* 根基菜品id查询对应的口味数据
* @param dishId
* @return
*/
@Select("select * from dish_flavor where dish_id = #{dishId}")
List<DishFlavor> getByDishId(long dishId);
}
根据id修改菜品
Controller
/**
* 菜品管理
*/
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
/**
* 修改菜品
*
* @param dishDTO
* @return
*/
@PutMapping
@ApiOperation("修改菜品")
public Result update(@RequestBody DishDTO dishDTO) {
log.info("修改菜品: {}", dishDTO);
dishService.updateWithFlavor(dishDTO);
//删除缓存数据
cleanCache("dish_*");
return Result.success();
}
}
Service
public interface DishService {
/**
* 修改菜品和对应的口味
* @param dishDTO
*/
void updateWithFlavor(DishDTO dishDTO);
}
@Service
@Slf4j
public class DishServiceImpl implements DishService {
@Autowired
private DishMapper dishMapper;
@Autowired
private DishFlavorMapper dishFlavorMapper;
/**
* 修改菜品和对应的口味
*
* @param dishDTO
*/
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) {
for (DishFlavor flavor : flavors) {
flavor.setDishId(dishDTO.getId());
}
// 向口味表插入n条数据
dishFlavorMapper.insertBatch(flavors);
}
}
}
Mapper
@Mapper
public interface DishMapper {
/**
* 根据id修改菜品数据
* @param dish
*/
@AutoFill(value = OperationType.UPDATE)
void update(Dish dish);
}
<?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">
<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>
</mapper>
@Mapper
public interface DishFlavorMapper {
/**
* 批量插入口味数据
* @param flavors
*/
void insertBatch( List<DishFlavor> flavors);
/**
* 根据id删除对应的口味数据
* @param dishId
*/
@Delete("delete from dish_flavor where dish_id = #{dishId}")
void deleteByDishId(Long dishId);
}
<?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>