公共字段自动填充
问题分析
我们在进行一些新增修改操作时,我们需要设置创建时间、创建人、修改时间、修改人等字段。
这些字段属于公共字段,也就是也就是在我们的系统中很多表中都会有这些字段,如下:
序号 | 字段名 | 含义 | 数据类型 |
---|---|---|---|
1 | create_time | 创建时间 | datetime |
2 | create_user | 创建人id | bigint |
3 | update_time | 修改时间 | datetime |
4 | update_user | 修改人id | bigint |
而针对于这些字段,我们的赋值方式为:
1). 在新增数据时, 将createTime、updateTime 设置为当前时间, createUser、updateUser设置为当前登录用户ID。
2). 在更新数据时, 将updateTime 设置为当前时间, updateUser设置为当前登录用户ID。
如下操作:
目前,在我们的项目中处理这些字段都是在每一个业务方法中进行赋值操作,如下:
新增员工方法:
/*** 新增员工** @param employeeDTO*/public void save(EmployeeDTO employeeDTO) {//.......................////设置当前记录的创建时间和修改时间employee.setCreateTime(LocalDateTime.now());employee.setUpdateTime(LocalDateTime.now());//设置当前记录创建人id和修改人idemployee.setCreateUser(BaseContext.getCurrentId());//目前写个假数据,后期修改employee.setUpdateUser(BaseContext.getCurrentId());///employeeMapper.insert(employee);}
编辑员工方法:
/*** 编辑员工信息** @param employeeDTO*/public void update(EmployeeDTO employeeDTO) {//........................................///employee.setUpdateTime(LocalDateTime.now());employee.setUpdateUser(BaseContext.getCurrentId());///employeeMapper.update(employee);}
如果都按照上述的操作方式来处理这些公共字段, 需要在每一个业务方法中进行操作, 编码相对冗余、繁琐,那能不能对于这些公共字段在某个地方统一处理,来简化开发呢?
答案是可以的,我们使用AOP切面编程,实现功能增强,来完成公共字段自动填充功能。
实现思路
在实现公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。在上述的问题分析中,我们提到有四个公共字段,需要在新增/更新中进行赋值操作, 具体情况如下:
序号 | 字段名 | 含义 | 数据类型 | 操作类型 |
---|---|---|---|---|
1 | create_time | 创建时间 | datetime | insert |
2 | create_user | 创建人id | bigint | insert |
3 | update_time | 修改时间 | datetime | insert、update |
4 | update_user | 修改人id | bigint |
实现步骤:
1). 自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法
2). 自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值
3). 在 Mapper 的方法上加入 AutoFill 注解
若要实现上述步骤,需掌握以下知识
枚举、注解、AOP、反射
第一步:自定义注解 AutoFill
/*** 自定义注解,用于标识某个方法需要进行功能字段自动填充处理*/
@Target(ElementType.METHOD) //指定注解只能加在方法上
@Retention(RetentionPolicy.RUNTIME) //用于描述注解的生命周期
public @interface AutoFill {//指定一个属性,指定当前数据库的操作类型,使用枚举OperationType value();}
OperationType是定义的一个公共枚举
/*** 数据库操作类型*/
public enum OperationType {/*** 更新操作*/UPDATE,/*** 插入操作*/INSERT}
第二步:自定义切面 AutoFillAspect
@Aspect //添加切面注解
@Component //标识一个类作为组件被Spring管理
@Slf4j
public class AutoFillAspect {/*** @description 定义一个切入点* @author 小辰* @throws//参数是写切入点表达式 * =返回值是所有的= com.sy.mapper =拦截mapper包中的方法= .* = 包下所有的类 =.* =所有的方法= (..) = 匹配所有的参数类型 =@annotation(com.sy.annotation.AutoFill) 也要满足AutoFill中定义的方法才生效,其他方法不生效* @time 2023/8/5 12:26*/@Pointcut("execution(* com.sy.mapper.*.*(..)) && @annotation(com.sy.annotation.AutoFill)")public void autoFillPointCut(){}//在执行sql之前使用,所以用前置通知.在通知中进行公共字段赋值@Before("autoFillPointCut()") //指定切入点public void autoFill(JoinPoint joinPoint){//通过连接点可以获取到拦截方法和方法参数log.info("开始公共字段自动填充...");//获取到当前被拦截的方法上的数据库操作类型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){//填充四个公共字段赋值try {Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);Method setCrateUser = 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);setCrateUser.invoke(entity,currentId);setUpdateTime.invoke(entity,now);setUpdateUser.invoke(entity,currentId);} catch (Exception e) {e.printStackTrace();}}else if(operationType == OperationType.UPDATE){//为两个公共字段赋值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();}}}
}
第三步:在Mapper接口的方法上加入 自定义注解AutoFill
分别在新增和修改方法添加@AutoFill()注解
/*** 插入数据* @param category*/@Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user)" +" VALUES" +" (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")@AutoFill(value = OperationType.INSERT)void insert(Category category);/*** 根据id修改分类* @param category*/@AutoFill(value = OperationType.UPDATE)void update(Category category);
第四步:注解之前的赋值代码
同时,将业务层为公共字段赋值的代码注释掉。
最后调试,代码运行成功!!!
结束!!!