内容纲要
背景
平时项目的需求上少不了一些对数据库表的CRUD,为了数据的安全和管理,各个表中难免会存在一些公共的字段如create_time
create_user
update_time
updete_user
来对各个操作进行追踪管理,如果每次更新字段都需要我们在service
层进行操作的话,代码出现冗余,且如果后期表字段出现变更,维护成本也无法保证。这时遍催生了新的需求,就是将这些公共字段实现自动填充。
实现思路
使用AOP切面编程来实现功能增强!来完成对公共字段的自动填充!
实现步骤:
1.自定义注解 AutoFill
,用于标识需要进行公共字段自动填充的方法
2.自定义切面类AutoFillAspect
,统一拦截加入了AutoFill
注解的方法,通过反射为公共字段赋值
3.在Mapper
的方法上加入AutoFill
注解
其主要技术点在:枚举、注解、AOP、反射
具体实现
因为这些自动填充字段更新的操作只涉及到插入(INSERT)和更新(UPDATE),所以需要先定义枚举类:
/**
* @description 数据库操作类型
* @author LoungeXi
*/
public enum OperationType {
/**
* 更新操作
*/
UPDATE,
/**
* 插入操作
*/
INSERT
}
自定义注解AutoFill:
/**
* @description 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
* @author LoungeXi
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
//数据库操作类型:UPDATE INSERT
OperationType value();
}
定义切面类:
/**
* @author LoungeXi
* @description 自定义切面类 实现公共字段自动填充
*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* @description 切入点
* @author LoungeXi
* @params * @param
*/
@Pointcut("execution(* cn.loungexi.mapper.*.*(..)) && @annotation(cn.loungexi.annotation.AutoFill)") //路径换成自己本地路径
public void autoFillPointCut() {}
/**
* @description 前置通知,在通知中对公共字段进行赋值
* @author LoungeXi
* @params * @param
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint) throws Exception {
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 {
// 获取方法 AutoFillConstant.SET_CREATE_TIME是实体类的set方法名 下面类似
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) {
throw new Exception(e);
}
}else if (operationType == OperationType.UPDATE){
// 为两个公共字段赋值
try {
// 获取方法 AutoFillConstant.SET_UPDATE_TIME是实体类的set方法名 下面类似
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 (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
}
在Mapper接口的方法上加入 AutoFill 注解,如:
/**
* @author LoungeXi
* @description 分类Mapper接口
*/
@Mapper
public interface CategoryMapper {
@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) ;
@AutoFill(value = OperationType.UPDATE)
void update(Category category);
}
至此需求完成。