目录
公共字段填充
由于在对员工数据进行操作的时候,前端传入的DTO将其属性值拷贝至对应的实体类时还会有其余属性是NULL,例如create_time、update_time等。每一次我们都要为其赋值后才能进行对应的更新或插入操作。代码出现大量重复的情况,如何处理呢?
-
解决思路
当某个mapper将要进行操作时,使用切面统一拦截并为公共字段统一赋值。需要注意这里只有插入和更新操作需要拦截,我们可以自定义注解,表示当前这个操作需要被拦截需要为其添公共字段。
步骤:自定义注解AutoFill,用于标识需要进行公共字段自动填充的方法
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
OperationType value();
}
/**
* 数据库操作类型
*/
public enum OperationType {
/**
* 更新操作
*/
UPDATE,
/**
* 插入操作
*/
INSERT
}
自定义切面类AutoFillAspect,统一拦截加入了AutoFill 注解的方法,通过反射为公共字段赋值
自定义切面类上面需要使用@Aspect注解申明它是一个切面,然后使用@Componet交给容器管理。
@Aspect
@Component
@Slf4j
public class AutoFillAspect
之后写一个切入点用注解@Pointcut(“切点表达式 && @annotation”)标志其为切入点,表示当匹配上对应切入点和注解的方法都会捕捉到。
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill )")
public void autoFillPointCut(){}
接着,用@before这个注解表示前置通知,完成统一赋值的操作。思路:获取到当前被拦截的方法上的数据库操作类型------->获取当前被拦截的方法的参数(实体对象)-------> 准备赋值的数据库------->根据当前不同的操作类型,为对应的属性通过反射来赋值。
public void autoFill(JoinPoint joinPoint) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
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){
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);
setUpdateUser.invoke(entity,currentId);
setUpdateTime.invoke(entity,now);
} 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);
setUpdateUser.invoke(entity,currentId);
setUpdateTime.invoke(entity,now);
}
}
在Mapper的方法上加入AutoFill注解
@AutoFill(value = OperationType.UPDATE)
void update(Employee employee);
新增菜品功能
-
基础代码编写
根据产品原型设计接口------>三个接口
对于图片这一块因为没有OSS所以直接在百度上找了一个图片并把图片的连接复制下来,当操作这一接口的时候我们直接返回图品连接(如果图片没有设置防盗链)可以直接在小程序端展示。
@PostMapping("/upload")
public Result<String> upload(){
//TODO 使用阿里云完成图片的回显
String url="photo_address";
return Result.success(url);
}
}
后期也可以自己手动搜索对应的图片放到数据库中,这样小程序就好看了。
用什么方法请求?传入什么参数?返回什么数据?
对于新增菜品接口,使用的是Post方式请求,传入的是json格式的DishDTO对象,无额外数据返回。相较于之前的插入操作,这次由于菜品的存储设计两张数据表因此开启事务保证原子性即操作要么全成功要么全部失败。使用@Transactional开启事务。
//serviceimpl
@Transactional
public void saveWithFlavor(DishDTO dishDTO){
.....
}
//XML文件
<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>
对于口味一条菜品可能会对应多种口味数据,这些口味数据根据菜品ID与对于的菜品联系起来。对于新增的菜品,我们不知道其菜品ID,那我们要怎么操作呢?
通过useGeneratedKeys="true" keyProperty="id",前者表示生成主键值,后者表示将生产的主键值返回给实体类名为id的属性值。
对口味我们采用的是批量插入的操作,前端提交的DishDTO中有一个list集合,我们讲里面的每一项都插入,因此要使用动态的sql里面的<foreach>。
<foreach collection="" close="" index="" item="" open="" separator="">
...
</foreach>
item里面填的是遍历的每一个元素的别名,用户操作时获取。open是以什么开始,close以什么作为结束表示,separator是分隔符,collection就是要迭代遍历的对象。
<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>
-
扩展
MP的批量插入的方式
需要使用真正的MP的批量插入我们需要自己先定义一个sql注入器。
@Component
public class SpiceSqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
List<AbstractMethod> methodList=super.getMethodList(mapperClass);
methodList.add(new InsertBatchSomeColumn(t -> !t.isLogicDelete() && !"update_time".equals(t.getColumn())));
return methodList;
}
}
之后在mapper中定义一个批量插入的方法。注意这里的methodList.add(new 类名)要和mapper方法中的名称是一样的如 InsertBatchSomeColumn和inserBatchSomeColum否者会报错。
@Mapper
public interface DIshMapper extends BaseMapper<DishFlavor> {
int insertBatchSomeColumn(List<DishFlavor> entityList);
}
详细的教程可以观看Mybatis-plus实现批量插入。
最终的效果就如下图
菜品分页查询功能
-
基础代码编写
用什么方法请求?传入什么参数?返回什么数据?
由于是分页查询需要页数,当前页以及额外的条件查询所以设计了一个DTO来接受前端传入的参数。并且返回的数据除了菜品信息还有分类信息因此创建了一个VO以后用于返回给前端。因此该方法使用get方式请求,传入的是DTO对象,返回的是还有VO的PageResult。
菜品的分页查询主要的难点就是需要查两张表,将dish表与category表进行左连接然后再根据动态条件查询。
<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>
</select>
删除菜品功能
-
基础代码编写
用什么方法请求?传入什么参数?返回什么数据?
使用的是delete方式请求,需要传入List集合,没有额外需要放回的数据。在参数前面需要添加@RequestParam后spring mvc框架才会自动解析字符串变成List集合。
public Result delete(@RequestParam List<Long> ids)
删除的业务逻辑:首先需要判断菜品是否是起售状态,起售状态的菜品不能够被删除。此外,如果菜品包含在一个套餐中也是不能被删除的。之后就是可以删除的情况了,删除菜品的同时也要删除对应口味。
-
扩展
MP方式的批量删除
@Override
public void delete() {
//模拟传进来的数据
List<Long> ids=new ArrayList<>();
ids.add(72L);
ids.add(46L);
QueryWrapper qw=new QueryWrapper<>();
qw.in("dish_id",ids);
dIshMapper.delete(qw);
}
修改菜品功能
-
基础代码编写
由产品原型我们得知需要设计四个接口,其中两个接口我们已经完成。
-
回显操作(根据id查询菜品)
用什么方法请求?传入什么参数?返回什么数据?
查询操作使用get的请求方式,需要传入id值,返回dish实体类和flavor实体类的信息因此设计了DishVO封装数据后给前端。
- 修改菜品
用什么方法请求?传入什么参数?返回什么数据?
使用update方式请求,传入的dishDTO,无需额外的数据返回。需要分别对口味和菜品信息分别修改,口味有可能要修改有可能不修改。这里的思路是:无论前端是否修改了口味,都先将口味删除,如果前端没有修改,保持原有回显的数据,最后在更新口味。
关于MP更新的操作可以参看DAY2关于编辑员工的操作 day2。