外卖开发(三)开发笔记
- 一、AOP实现实现公共字段填充(减少重复工作)
- 实现思路
- 自定义注解AutoFill
- 自定义切面AutoFillAspect
- 在Mapper接口上添加`@AutoFill`注解
- 二、主键回显情况
- 三、抛异常 和 事务管理
一、AOP实现实现公共字段填充(减少重复工作)
实现思路
1、自定义注解@AutoFill
,用于表示需要进行公共字段填充的方法
2、自定义切面类,AutoFillAspect
,统一拦截加入了@AutoFill
注解的方法,通过反射为公共字段赋值。
3、在Mapper方法上加入@AutoFill
注解,因为这里我们的公共字段是更新时间、更新人、创建时间、创建人,所以只在insert
、和update
操作时才需要进行AutoFill
。在进行Mapper方法前,先将实体类对象的相关属性填充,然后在进行insert
和update
(before前置通知)
自定义注解AutoFill
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {/*** 定义注解的value值,对应数据操作类型insert 和 update* @return*/OperationType value();
}
自定义枚举
/*** 数据库操作类型*/
public enum OperationType {/*** 更新操作*/UPDATE,/*** 插入操作*/INSERT}
自定义切面AutoFillAspect
/*** 自定义通知类,实现公共字段填充*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {@Pointcut("@annotation(com.sky.annotation.AutoFill)")public void pointCut(){}@Before("pointCut()")public void autoFill(JoinPoint joinPoint) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {log.info("开始进行字段填充");//获取当前被拦截方法的数据库操作类型MethodSignature signature = (MethodSignature) joinPoint.getSignature();AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);OperationType operationType = autoFill.value();//获取当前方法的参数--实体类对象 反射Object[] pointArgs = joinPoint.getArgs();if(pointArgs[0] == null){return;}Object entity = pointArgs[0];LocalDateTime now = LocalDateTime.now();Long currentId = BaseContext.getCurrentId();//根据不同的操作类型,为对应的属性通过反射来赋值if(operationType == OperationType.INSERT){Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);//反射为属性赋值setCreateTime.invoke(entity,now);setCreateUser.invoke(entity,currentId);setUpdateTime.invoke(entity,now);setUpdateUser.invoke(entity,currentId);}else if(operationType == OperationType.UPDATE){Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);//反射为属性赋值setUpdateTime.invoke(entity,now);setUpdateUser.invoke(entity,currentId);}}
}
在Mapper接口上添加@AutoFill
注解
此时,我们就不需要在service中重复进行字段的填充。
二、主键回显情况
如:新增菜品
涉及到了两张表,dish表和dish_flavor表。
dish表
dish_flavor表
DishDTO.java
public class DishDTO implements Serializable {private Long id;//菜品名称private String name;//菜品分类idprivate Long categoryId;//菜品价格private BigDecimal price;//图片private String image;//描述信息private String description;//0 停售 1 起售private Integer status;//口味private List<DishFlavor> flavors = new ArrayList<>();}
分析:新增dish操作,需要进行两次insert
,分别插入dish表和dish_flavor表,但是我们从前端接收到的数据中(DishDTO)
,List<DishFlavor>
中只包含了我们新增的口味名称
和口味值
,并不会包含对应的dish_id
,那么我们就需要把第一次向dish表中插入新数据时自动生成的id回显(带回来),并付给List<DishFlavor>
中的dish_id。
DishService.java
/*** 新增菜品* @param dishDTO*/@Overridepublic void insertDish(DishDTO dishDTO) {Dish dish = new Dish();BeanUtils.copyProperties(dishDTO,dish);dishMapper.insert(dish);//获取inser之后的主键Long dishId = dish.getId();List<DishFlavor> flavors = dishDTO.getFlavors();if(flavors != null && flavors.size()>0){flavors.forEach(dishFlavor -> {dishFlavor.setDishId(dishId); //将回显的主键id赋给flavir中的dish_id});dishFlavorMapper.insertBatch(flavors);}}
/*** 批量新增dish* @param flavors*/@AutoFill(OperationType.INSERT)void insert(Dish dish);/*** 批量新增dish_flavor* @param flavors*/void insertBatch(List<DishFlavor> flavors);
<!--插入新菜品--><insert id="insert" useGeneratedKeys="true" keyProperty="id"> <!--主键回显-->insert into dish (name,category_id,price,image,description,status,create_time,update_time,create_user,update_user)values(#{name},#{categoryId},#{price},#{image},#{description},#{status},#{createTime},#{updateTime},#{createUser},#{updateUser})</insert>
三、抛异常 和 事务管理
在删除某个菜品的时候,如果这个菜品的状态为起售(status=1)
时,就无法进行删除,需要停止删除操作,并抛出相关异常
。如果这个菜品正在被一些套餐关联,那么也不能删除。
在删除菜品的时候,也要删除相关的菜品对应的口味,所以需要删除两个表。显而易见,我们需要进行事务管理
!
/*** 批量删除菜品* @param ids*/@Override@Transactional //开启事务public void deleteDish(List<Long> ids) {//是否存在起售中的 存在就不能删除//利用count(1)计算出准备删除的菜品中status=1 的数量,如果大于0,说明存在起售中的Integer status = dishMapper.queryStatus(ids); if (status > 0){//抛出自定义的异常和异常信息:无法删除存在起售的菜品throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);}//查询有没有关联套餐 关联就不能删除//使用count(1)查询套餐中对应dish是否存在,如果大于0则不能删除Integer integer = setmealDishMapper.countDish(ids);if(integer > 0){//抛出自定义的异常和异常信息:无法删除存在关联的菜品throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);}//删除dish表中的数据dishMapper.deleteDish(ids);//删除dish-flavor表dishFlavorMapper.deleteByDishId(ids);}