【SpringBoot+MP】针对复杂业务来手动封装一些涉及到多表操作的删除、分页查询方法

时间:2022-10-23 19:56:23

前言

最近也是遇到了一些比较复杂的业务,MP内部提供的方法显然已经不能解决问题,针对场景需要自己手动封装一些方法来用,也是让自己明白了项目不单单都是简单的CRUD,涉及到多表还是比较复杂。

一.扩展MP提供的方法

场景一(删除)

在以前学习OOP中继承的时候,讲到在一组继承关系中,为了提高代码复用性,可扩展父类的方法。
大家都知道,MP的特色就是开发者不用写SQL,而这背后的原理是在一次又一次接口实现、类的继承中体现的(直接调用事先封装好的方法),那如果他提供的方法不能用于特定场景,是不是也可以在接口中扩展一下。
【SpringBoot+MP】针对复杂业务来手动封装一些涉及到多表操作的删除、分页查询方法
既然并不能帮助我们解决万难,那么在一些特殊的场景我们不能局限于使用它提供的方法,针对场景要进行适当“改装”。所以,当“时机成熟之时”我们可以扩展父类的方法,扩展功能

【SpringBoot+MP】针对复杂业务来手动封装一些涉及到多表操作的删除、分页查询方法
当实体间存在联系,也就是几张表之间相互关联。如果想要删除表中的一则信息我们肯定是要考虑他与其他实体的联系,比如:我删除一个菜类表中的一条数据,这个菜类下可能有菜品,也可能关联套餐,如果直接remove掉选中的菜类,那么我另两个实体中的数据就会丢失!
【SpringBoot+MP】针对复杂业务来手动封装一些涉及到多表操作的删除、分页查询方法
所以,我如果想要删除菜类的数据,直接通过MP提供的remove()方法显然是不够严谨全面的。在通过service调用remove()方法之前得加上贴合实际场景的判断条件。
这就要求我们对MP接口里的方法进行扩展:
怎么改进呢?反正层与层之间是继承、实现,那我在接口里重新定义一个remove()方法,不去用他给我提供的不就????了

【SpringBoot+MP】针对复杂业务来手动封装一些涉及到多表操作的删除、分页查询方法
类似于这样一个场景:
此时,前端已经向我们发来了请求:
【SpringBoot+MP】针对复杂业务来手动封装一些涉及到多表操作的删除、分页查询方法
在其实现类里,重写该方法,并加上针对“是否关联菜类,套餐”做一个逻辑判断,若不关联则直接调用父类的方法删除(直接删除无影响),反之则进入if()判断条件,抛出异常并终止删除操作!
怎么才能表示他有关联菜品,套餐呢?
直接根据相同的id查询就可,并返回查询到的行数是否大于0来作为判断条件
在实现类中,通过MP实现起来也就变得十分清晰明了,简简单单,就像这样:

/**
 * 这个方法是我手动封装的!改装的!为了就是解决特殊情况
 * 接收来自前端-接口-实现类的形参中ids参数并进行等值查询
 * @param ids
 */
@Override
public void remove(Long ids) {

    //条件构造器
    LambdaQueryWrapper<Dish> lqw1 = new LambdaQueryWrapper<>();
    //添加查询条件
    lqw1.eq(Dish::getCategoryId, ids);
    //返回查询行数
    int count1 = dishService.count(lqw1);
    //查询当前分类是否关联了菜品,如果关联就抛出一个业务异常
    if (count1 > 0) {
        //已将关联菜品,则抛出一个定义好的业务异常
        throw new CustomException("菜类已关联彩菜品,无法删除");
    }
    LambdaQueryWrapper<Setmeal> lqw2 = new LambdaQueryWrapper<>();
    lqw2.eq(Setmeal::getCategoryId, ids);
    int count2 = setmealService.count(lqw2);
    if (count2 > 0) {
        throw new CustomException("菜类已关联套餐,无法删除");
    }
    //如果不关联  则直接用框架的方法根据id删除它
    super.removeById(ids);
}

上述的方法已经被我写到了实现类中,在Controller层里我们注入该类对应的接口即可使用自己扩展的remove()方法了!
【SpringBoot+MP】针对复杂业务来手动封装一些涉及到多表操作的删除、分页查询方法

二.多表操作与事务

场景二(保存)

同样地,在一个保存前端信息的场景中,由于前端的信息涉及到了两张表,我的实体不能一次性封装所有的数据,我需要扩展实体类来封装信息,而这就涉及到了多表的操作,也需要在Service层接口中扩展一个新的方法来处理两张表的信息。

首先定义一个新的DishDto实体类,通过继承获得了Dish的属性,为了能够保存DishFlavor表中的属性我在此类中做出如下扩展:

@Data
public class DishDto extends Dish {
    //用于数据传输 由于Dish中没有flavor属性,所以需要此类来扩展此类
    private List<DishFlavor> flavors=new ArrayList<DishFlavor>(); //接收页面提交的flavor属性
}

经常使用MP的都知道,一般都是一张表对应一个实体类和一个Service,为了少写不必要的表,我直接在在形参里传入DishDto来封装前端的数据,然后在方法里操作DishDto中属于各自表的信息

【SpringBoot+MP】针对复杂业务来手动封装一些涉及到多表操作的删除、分页查询方法
那么,在方法里是怎么操作多表的?
【SpringBoot+MP】针对复杂业务来手动封装一些涉及到多表操作的删除、分页查询方法
前端的信息已经被封装到了形参中的实体,为了将菜品的基本信息保存到dish表可以直接用Dish的Service对象来调用save()方法,为了保存DishFlavor表中的属性到dish_flavor表则是通过DishFlavor的service来调用saveBatch()保存flavor集合

@Override
@Transactional  //由于涉及到多张表的操作,这里要开启事务
public void saveWithFlavor(DishDto dishDto) {

    //保存菜品基本信息到dish表
    this.save(dishDto);//在这个类里我直接用this

    Long dishId = dishDto.getId();//菜品id

    //菜品口味
    //由于少封装了dishId这里要遍历集合补充一下
    List<DishFlavor> flavors = dishDto.getFlavors();
    flavors=flavors.stream().map((temp)->{  //  stream流来遍历
        temp.setDishId(dishId);
        return temp;
    }).collect(Collectors.toList());

    //保存菜品口味到dish_flavor,保存集合用saveBatch()方法,dishDto.getFlavors()得到口味集合
    dishFlavorService.saveBatch(flavors);
}

有人可能会问,上面保存flavor字段的操作为什么比较复杂,甚至还需要遍历?

由于Java的单继承的机制,自定义的DTO类只继承了Dish类,而这就导致该类丢失了dishId属性。因为DishFlavor表中的属性的保存要和dishId相绑定存入表中,所以在方法里我们需要遍历一遍,给flavor设置dishId。

有人可能会问,你为什么不在DishDto里直接新定义一个属性dishId呢?
【SpringBoot+MP】针对复杂业务来手动封装一些涉及到多表操作的删除、分页查询方法
因为我无法给Long型数据指定泛型,而且也没有对应的继承关系,所以只能在方法中通过set方法获得DishFlavor的dishId。

写到这里,已经又一次地在MP的基础上扩展完了方法,而使用到该方法则是在Controller层中直接调用来操作封装前端信息的实体,就像这样:

@PostMapping()
public R<String> save(@RequestBody DishDto dishDto) {
    dishService.saveWithFlavor(dishDto);
    return R.success("菜品信息保存成功!");
}

最后也是成功完成了保存:
【SpringBoot+MP】针对复杂业务来手动封装一些涉及到多表操作的删除、分页查询方法

场景三(修改)

在这样一个场景中:更新菜品信息的同时更新口味信息(两者不是一张表不能一次性更改)
【SpringBoot+MP】针对复杂业务来手动封装一些涉及到多表操作的删除、分页查询方法
为了实现这一功能,同样需要自己在接口里扩展方法
【SpringBoot+MP】针对复杂业务来手动封装一些涉及到多表操作的删除、分页查询方法
在实现类中,针对前端传来的实体类对应的数据,首先应更新菜品信息,也就是修改dish表,可以直接利用MP提供的updateById方法this.updateById(dishDto);来修改
其次,对于DishFlavor表中的属性也就是对dish_flavor表的操作就稍微复杂,得先删除菜品中的口味,后给菜品设置新的口味信息:
(主要通过遍历,来动态绑定id赋予口味信息给菜品)
首先从DishDto里拿到用户选择的口味信息,我们通过.getFlavor()方法拿到口味信息value和name并封装到集合里,由于未与dishId绑定所以我们需要遍历一遍集合,并将DishDto里的dishId赋给集合里的元素
两张表之间是通过dish表的id字段连接dish_flavor表中的dish_id,所以为了连接到,要将dish表的id动态地赋给dish_flavor表中的dish_id

    @Override
    @Transactional
    public void updateWithFlavor(DishDto dishDto) {
        //根据id选择修改
        this.updateById(dishDto);
        //新建查询,查询口味信息
        LambdaQueryWrapper<DishFlavor> dishDtoLambdaQueryWrapper = new LambdaQueryWrapper<>();
        //找出当前菜品对应的口味信息
        dishDtoLambdaQueryWrapper.eq(DishFlavor::getDishId, dishDto.getId());
        //删除指定菜品的口味信息
        dishFlavorService.remove(dishDtoLambdaQueryWrapper);
        //从DishDto里拿到用户选择的口味信息
        List<DishFlavor> flavors = dishDto.getFlavors();
        //遍历一遍flavors赋给他新的id
        flavors = flavors.stream().peek((temp) -> temp.setDishId(dishDto.getId())).collect(Collectors.toList());
        //保存口味信息到dish_flavor表中
        dishFlavorService.saveBatch(flavors);
    }

最后,在Controller层中直接调用扩展的方法,实现功能:
【SpringBoot+MP】针对复杂业务来手动封装一些涉及到多表操作的删除、分页查询方法