外卖开发(三)开发笔记——AOP实现实现公共字段填充、主键回显、抛异常和事务管理
外卖开发(三)开发笔记
- 一、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;
//菜品分类id
private 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
*/
@Override
public 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);
}