在做项目的时候,很多数据库表会有相同字段,例如很多表都会记下数据的创造时间、修改时间、创造者、修改者,便于后期维护。如果每次对数据的增加和修改操作都需要手动添加后修改这些字段,则会有大量的相同代码,也影响了代码的可读性。为此,我们可以利用反射机制和AOP(面向切面编程),对这些公共字段进行统一处理
| 序号 |
含义 |
数据类型 |
操作 |
| 1 |
创建时间 |
datetime |
insert |
| 2 |
创建人id |
bigint |
insert |
| 3 |
修改时间 |
datetime |
insert、update |
| 4 |
修改人id |
bigint |
insert、update |
解决方案
- 自定义注解AutoFill,用于标识需要进行公共字段自动填充的方法
- 自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射为公共字段赋值
- 在Mapper的方法上加入AutoFill注解
技术点: 枚举、注解、AOP、反射
自定义注解AutoFill
1 2 3 4 5 6 7 8 9
|
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AutoFill { OperationType value(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
public enum OperationType {
UPDATE,
INSERT }
|
OperationType 是自定义的枚举类
@Target 和 @Retention,可以用来修饰注解,是注解的注解,被称为元注解
@Target : 目标,即该注解可以声明在哪些目标元素之前,也可理解为注释类型的程序元素的种类
- ElementType.PACKAGE:该注解只能声明在一个包名前
- ElementType.ANNOTATION_TYPE:该注解只能声明在一个注解类型前
- ElementType.TYPE:该注解只能声明在一个类前
- ElementType.CONSTRUCTOR:该注解只能声明在一个类的构造方法前
- ElementType.LOCAL_VARIABLE:该注解只能声明在一个局部变量前
- ElementType.METHOD:该注解只能声明在一个类的方法前
- ElementType.PARAMETER:该注解只能声明在一个方法参数前
- ElementType.FIELD:该注解只能声明在一个类的字段前
**@Retention :保留,可以理解为如何保留,即告诉编译程序如何处理,也可理解为注解类的生命周期 **
- RetentionPolicy.SOURCE : 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃
- RetentionPolicy.CLASS : 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期
- RetentionPolicy.RUNTIME : 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
自定义切面类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| ** * @description: 自定义切面 */ @Aspect @Component @Slf4j public class AutoFillAspect {
@Pointcut("@annotation(com.sky.annotation.AutoFill)") public void autoFillPointCut(){}
@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 setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class); Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class); Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_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) { 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(); } }
}
}
|
在执行update和save方法前就需要对公共字段赋值,所以这里使用的是前置通知
@Pointcut(“@annotation(com.sky.annotation.AutoFill)”),com.sky.annotation.AutoFill 是自定义注解AutoFill类的位置
update方法只需要为2个公共字段赋值,save方法需要为4个公共字段赋值
在Mapper的方法上加入AutoFill注解
1 2 3 4 5 6 7 8 9
| @AutoFill(value = OperationType.INSERT) @Insert("insert into employee (name, username, password, phone, sex, id_number, status, create_time, update_time, create_user, update_user) " + "values " + "(#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{status},#{createTime},#{updateTime},#{createUser},#{updateUser})") void insert(Employee employee);
@AutoFill(value = OperationType.UPDATE) void update(Employee employee);
|