java保存实现变更记录
前言
最近遇到一个需求,要求编写保存变更记录的功能,记录下哪些字段由什么值变为了什么值,经过参考网上的解决方案,将部分核心代码记录下来。
实现方案
- 自定义注解@LogCompar:配置了要比较的字段名称,日期格式,码值转换,默认值等;
- 编写工具类:通过java反射机制,获取要比较对象中所有添加了@LogCompar的属性,并进行比较记录;
- 单元测试:要比较的实体类添加@LogCompar注解,测试。
代码实现
- 创建自定义注解:
/**
* @Describe 变更记录注解:用于注释在要比较变更的对象的属性上
* @Author xlma
* @Date 2023/02/06 21:45
* @Version 1.0
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogCompar {
/**
* 属性名称
*/
String value();
/**
* 日期格式, 如: yyyy-MM-dd
*/
String dateFormat() default "";
/**
* 读取内容转表达式 (如: 0=男,1=女,2=未知)
*/
String readConverterExp() default "";
/**
* 分隔符,readConverterExp表达式的分隔符
*/
String separator() default ",";
/**
* 当值为空时的默认值
*/
String defaultValue() default "null";
}
- 编写工具类
/**
* @Describe 添加变更记录工具类
* @Author xlma
* @Date 2023/2/6 21:32
* @Version 1.0
*/
@Slf4j
public class RecordUtils {
/**
* 注解列表(添加了@LogCompar的属性集合,数组的第一位是属性Field,第二位是注解LogCompar)
*/
private static List<Object[]> fields;
/**
* 比较变更前后的数据,记录下添加了@LogCompar注解的属性,由原来的什么值变为了现在的什么值
*
* @param oldObj 原数据对象
* @param newObj 新数据对象
*/
public static String addChangeRecord(Object oldObj, Object newObj) {
try {
// 获取Class对象
Class<?> oldClazz = oldObj.getClass();
Class<?> newClazz = newObj.getClass();
if (!oldClazz.equals(newClazz)) {
throw new RuntimeException("请传入两个相同的实体类对象!");
}
// 获取加了注解的属性集合
fields = getFields(oldClazz);
if (fields.size() == 0) {
throw new RuntimeException("没有找到要对比的属性!");
}
// 定义变量:变更描述信息
StringBuilder info = new StringBuilder();
// 根据注解列表,循环比较属性是否变化
for (Object[] os : fields) {
Field field = (Field) os[0];
LogCompar logCompar = (LogCompar) os[1];
// 获取新旧的属性值
Object oldValue = field.get(oldObj);
Object newValue = field.get(newObj);
if (oldValue == null) {
oldValue = logCompar.defaultValue();
}
if (newValue == null) {
newValue = logCompar.defaultValue();
}
// 若新值和旧值相等,则跳过
if (oldValue == newValue || newValue.equals(oldValue)) {
continue;
}
// 若是BigDecimal类型,需要compareTo方法判断是否相等,相等则跳过
if (field.getType() == BigDecimal.class && ((BigDecimal) newValue).compareTo((BigDecimal) oldValue) == 0) {
continue;
}
// 若属性值有变化,则判断是否有码值,
if (StringUtils.isNotBlank(logCompar.readConverterExp())) {
oldValue = convertByExp(oldValue.toString(), logCompar.readConverterExp(), logCompar.separator());
newValue = convertByExp(newValue.toString(), logCompar.readConverterExp(), logCompar.separator());
} else if (StringUtils.isNotBlank(logCompar.dateFormat())) {
oldValue = DateHelper.format(oldValue, logCompar.dateFormat());
newValue = DateHelper.format(newValue, logCompar.dateFormat());
}
info.append(String.format("%s:由%s 变为了 %s;", logCompar.value(), oldValue, newValue));
}
// 此处可以返回info或直接保存日志表处理
return info.toString();
} catch (IllegalAccessException e) {
log.error(e.getMessage());
throw new RuntimeException(e.getMessage());
}
}
/**
* 获取注解属性信息
*/
private static List<Object[]> getFields(Class<?> clazz) {
// 定义加了@LogCompar注解的属性集合
List<Object[]> fields = new ArrayList<Object[]>();
// 定义临时的属性集合,包含了当前类及其父类的所有属性
List<Field> tempFields = new ArrayList<>();
tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
// 遍历临时属性集合,判断遍历出的属性上是否有@LogCompar注解,若有,则将当前属性及注解添加到fields中
for (Field field : tempFields) {
// 若当前属性field上添加了@LogCompar注解,则isAnnotationPresent()方法返回true
if (field.isAnnotationPresent(LogCompar.class)) {
LogCompar logCompar = field.getAnnotation(LogCompar.class);
if (logCompar != null) {
// 设置可以访问私有private的属性
field.setAccessible(true);
fields.add(new Object[]{field, logCompar});
}
}
}
return fields;
}
/**
* 解析导出值 0=男,1=女,2=未知
*
* @param propertyValue 参数值
* @param converterExp 翻译注解
* @param separator 分隔符
* @return 解析后值
*/
private static String convertByExp(String propertyValue, String converterExp, String separator) {
StringBuilder stringBuilder = new StringBuilder();
String[] convertSource = converterExp.split(",");
for (String item : convertSource) {
String[] itemArray = item.split("=");
if (StringUtils.containsAny(separator, propertyValue)) {
for (String value : propertyValue.split(separator)) {
if (itemArray[0].equals(value)) {
stringBuilder.append(itemArray[1] + separator);
break;
}
}
} else if (itemArray[0].equals(propertyValue)) {
return itemArray[1];
}
}
return StringUtils.stripEnd(stringBuilder.toString(), separator);
}
}
- 创建实体类,以用户表为例
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String username;
@LogCompar("真实姓名")
private String realName;
@LogCompar(value = "性别", readConverterExp = "0=男,1=女,2=未知")
private Integer sex;
@LogCompar("邮箱")
private String email;
@LogCompar(value = "薪水")
private BigDecimal salary;
@LogCompar(value = "创建时间", dateFormat = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
}
- 单元测试
@Test
void recordEncrypt() {
User oldUser = new User(1L, "admin", "张三", 1, "123@qq.com", BigDecimal.valueOf(12), LocalDateTime.now());
User newUser = new User(2L, "admin", "李四", 2, null, BigDecimal.valueOf(12.00), LocalDateTime.of(2022, 2, 12, 5, 12, 5));
System.out.println(RecordUtils.addChangeRecord(oldUser, newUser));
}
- 结果
真实姓名:由张三 变为了 李四;性别:由女 变为了 未知;邮箱:由123@qq.com 变为了 null;创建时间:由2023-03-12 13:53:03 变为了 2022-02-12 05:12:05;