目录
一、问题分析
当执行sql语句(增删改查)的时候,需要同时更新修改人以及修改时间,如果在每一次执行sql的时候在代码中添加该字段的话,代码会非常的冗余,不好管理,因此使用公共字段自动填充可以更好的实现该功能。

二、实现思路
通过切面进行统一处理,分为两种情况(新增和修改),查询和删除不用修改,新增需要更新四个字段,修改需要更新两个字段。


三、自定义注解实现
创建注解实现类:


自定义中注解代码如下:
java
package com.sky.annonation;
import com.sky.enumeration.OperationType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
OperationType value();
}
**@Target(ElementType.METHOD)**是说这个注解使用到方法上。
**@Retention(RetentionPolicy.RUNTIME)**是说这个注解在运行的时候起作用
OperationType value(); 是枚举类。
枚举类中代码如下:
java
package com.sky.enumeration;
/**
* 数据库操作类型
*/
public enum OperationType {
/**
* 更新操作
*/
UPDATE,
/**
* 插入操作
*/
INSERT
}
四、自定义切面实现类
java
package com.sky.aspect;
import com.sky.annonation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
@Component
@Aspect
@Slf4j
public class AutoFillAspect {
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annonation.AutoFill)")
public void AutoFill(){
log.info("开始进行数据填充");
}
@Before("AutoFill()")
public void before(JoinPoint joinPoint) throws NoSuchMethodException {
log.info("开始进行数据填充");
// 只有当修改和新增方法才需要拦截,获得数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
OperationType value = autoFill.value();
// 获得被拦截方法的参数
Object[] args = joinPoint.getArgs();
if(args == null || args.length == 0){
return;
}
Object object = args[0];
if(value == OperationType.INSERT){
LocalDateTime now = LocalDateTime.now();
Long id = BaseContext.getCurrentId();
Method declaredMethod = object.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME , LocalDateTime.class);
Method declaredMethod1 = object.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
Method declaredMethod2 = object.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method declaredMethod3 = object.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
try {
declaredMethod.invoke(object,now);
declaredMethod1.invoke(object,id);
declaredMethod2.invoke(object,now);
declaredMethod3.invoke(object,id);
} catch (Exception e) {
throw new RuntimeException(e);
}
}else if(value == OperationType.UPDATE){
LocalDateTime now = LocalDateTime.now();
Long id = BaseContext.getCurrentId();
Method declaredMethod = object.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME , LocalDateTime.class);
Method declaredMethod1 = object.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
try {
declaredMethod.invoke(object,now);
declaredMethod1.invoke(object,id);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
注明这个类是切面类,加入@aspect注解,@compent注解将这个类交给容器管理。
定义切入点:
java
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annonation.AutoFill)")
public void AutoFill(){
log.info("开始进行数据填充");
}
当mapper层级下的各个方法执行的时候就会执行这个切面类,或者加入@AutoFill注解的时候也会执行这个切面类。
java
@Before("AutoFill()")
public void before(JoinPoint joinPoint) throws NoSuchMethodException {
log.info("开始进行数据填充");
// 只有当修改和新增方法才需要拦截,获得数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
OperationType value = autoFill.value();
// 获得被拦截方法的参数
Object[] args = joinPoint.getArgs();
if(args == null || args.length == 0){
return;
}
Object object = args[0];
if(value == OperationType.INSERT){
LocalDateTime now = LocalDateTime.now();
Long id = BaseContext.getCurrentId();
Method declaredMethod = object.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME , LocalDateTime.class);
Method declaredMethod1 = object.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
Method declaredMethod2 = object.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method declaredMethod3 = object.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
try {
declaredMethod.invoke(object,now);
declaredMethod1.invoke(object,id);
declaredMethod2.invoke(object,now);
declaredMethod3.invoke(object,id);
} catch (Exception e) {
throw new RuntimeException(e);
}
}else if(value == OperationType.UPDATE){
LocalDateTime now = LocalDateTime.now();
Long id = BaseContext.getCurrentId();
Method declaredMethod = object.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME , LocalDateTime.class);
Method declaredMethod1 = object.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
try {
declaredMethod.invoke(object,now);
declaredMethod1.invoke(object,id);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
代码解释:
1、获取注解中的参数值,用来判断是新增还是修改。
2、获取mapper层中方法的参数值,获得到传入的实体类。
3、根据新增还是修改,利用反射为实体类中的修改人、修改时间、新增人、新增时间赋值。
五、代码测试
执行代码之后,利用调试工具我们可以看到

会标注好切入点,然后以及切入的方法是UPDATE还是INSERT。

获取实体类中的参数结果展示。
sql的执行结果如下:

六、其他相关类
java
package com.sky.context;
public class BaseContext {
public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setCurrentId(Long id) {
threadLocal.set(id);
}
public static Long getCurrentId() {
return threadLocal.get();
}
public static void removeCurrentId() {
threadLocal.remove();
}
}