公共字段填充
需要解决的问题
1). 在新增数据时, 将createTime、updateTime 设置为当前时间, createUser、updateUser设置为当前登录用户ID。
2). 在更新数据时, 将updateTime 设置为当前时间, updateUser设置为当前登录用户ID。
实现步骤
1). 自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法
自定义注解
/**
* 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
*/
@Target(ElementType.METHOD) // 注解只作用于方法上
@Retention(RetentionPolicy.RUNTIME) // 表示注解的生命周期
public @interface AutoFill {
// 指定数据库操作类型,UPDATE INSERT
OperationType value();
}
2). 自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值
AOP
/**
* 自定义切面,实现公共字段自动填充处理逻辑
*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 指定切入点
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)") // 切入点是com.sky.mapper包下的带任意参数的所有方法,但是此方法上需要有@AutoFill注解标识
public void autoFillPointCut() {}
/**
* 前置通知,在通知中进行公共字段的赋值
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint) {
log.info("开始进行公共字段的自动填充...");
// 获取当前被拦截的方法上的数据库操作类型
// Signature signature = joinPoint.getSignature(); 将Signature转为MethodSignature
MethodSignature signature = (MethodSignature) joinPoint.getSignature(); // 方法签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class); // 获得方法上的注解对象
OperationType operationType = autoFill.value(); // 获得数据库操作类型
// 获取到当前被拦截的方法的参数--实体对象(默认此参数在第一个参数位置)
Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0) {
return;
}
Object entity = args[0];
// 准备赋值的数据
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
// 根据当前不同的操作类型,为对应的属性通过反射来赋值
if (operationType == OperationType.INSERT) {
// 为4个公共字段赋值
try {
// Method setCreateTime = entity.getClass().getDeclaredMethod("setCreateTime", LocalDateTime.class);
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);
} catch (Exception e) {
e.printStackTrace();
}
} else if (operationType == OperationType.UPDATE) {
// 为2个公共字段赋值
try {
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);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
3). 在 Mapper 的方法上加入 AutoFill 注解
在具有update和insert操作的mapper的方法上加上@AutoFill注解,并指明value属性的具体值。
新增菜品
文件上传
(暂且存在问题)
新增菜品
service层代码
@Override
@Transactional // 需要先在启动类上开始注解事务
public void saveWithFlavor(DishDTO dishDTO) {
Dish dish = new Dish();
BeanUtils.copyProperties(dishDTO, dish);
// 向菜品表插入1条数据
dishMapper.insert(dish);
// 获取insert语句生成的主键值,在Mapper层的SQL语句上,必须先加上<insert id="insert" useGeneratedKeys="true" keyProperty="id">,指定主键自增
Long dishId = dish.getId();
List<DishFlavor> flavors = dishDTO.getFlavors();
if (flavors != null && flavors.size() > 0) {
flavors.forEach(dishFlavor -> {
dishFlavor.setDishId(dishId);
});
// 向口味表插入n条数据 批量插入
dishFlavorMapper.insertBatch(flavors);
}
}
分页查询菜品
删除菜品
service层代码
@Override
@Transactional
public void deleteBatch(List<Long> ids) {
// 判断当前菜品是否能够删除---是否存在启售中的菜品
for (Long id : ids) {
Dish dish = dishMapper.getById(id);
if (dish.getStatus().equals(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);
}
}
修改菜品
service层代码
注意先要进行数据回显,再进行修改,两个接口。
// 修改菜品信息
@Override
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) {
flavors.forEach(dishFlavor -> {
dishFlavor.setDishId(dishDTO.getId());
});
// 向口味表中插入n条数据
dishFlavorMapper.insertBatch(flavors);
}
}
启售/停售菜品
service层代码
// 跟资料略有一点不同
@Override
public void startOrStop(Long id, Integer status) {
// 进行停售操作
if (status.equals(StatusConstant.DISABLE)) {
List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishId(id);
// 判断是否有关联的套餐数据,如果有则将该套餐也随之停售。
if (setmealIds != null && setmealIds.size() != 0)
setmealIds.forEach(setmealId -> {
setmealMapper.update(
Setmeal.builder()
.id(setmealId)
.status(StatusConstant.DISABLE)
.build()
);
});
}
Dish dish = Dish.builder()
.id(id)
.status(status)
.build();
dishMapper.update(dish);
}