由于需要做一个详情日志的功能,就是需要记录每次修改,具体变动的属性值变化,所以需要这么一个工具
本次是站在数据库角度写的,如果新值为null,那么update语句不会更新这个属性
如果需求有变动,稍微调整即可
直接贴上全部代码
代码
package com.gly.memo.utils;
import cn.hutool.core.collection.CollUtil;
import com.gly.memo.common.exception.MyException;
import com.gly.memo.entity.Task;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.TemporalAccessor;
import java.util.*;
@Slf4j
public class ObjectUtils {
/**
* 用来缓存每个对象的所有属性
*/
private static final Map<String, Map<String, Field>> fieldMapCache = new HashMap<>();
/**
* 默认忽略的属性
*/
private static final Set<String> defaultIgnoreSet = CollUtil.newHashSet("serialVersionUID");
/**
* 以【newObj】为主,站在数据库【update语句】的角度记录变化的属性
* [oldObj: {aa: 11, bb: 22}] [newObj: {aa: 33, bb: 44}] --> [return: [{field: aa, before: 11, after: 33}, {field: bb, before: 22, after: 44}]]
* [oldObj: {aa: null, bb: 22}] [newObj: {aa: 33, bb: 44}] --> [return: [{field: aa, before: null, after: 33}, {field: bb, before: 22, after: 44}]]
* [oldObj: {aa: 11, bb: 22}] [newObj: {aa: null, bb: 44}] --> [return: {field: bb, before: 22, after: 44}]
*
* @param oldObj 旧值
* @param newObj 新值
* @param ignores 忽略属性
* @return 只有 newObj 存在、且与 oldObj 不同的属性才会返回,否则不会进行比较
*/
public static <T> List<Map<String, String>> diff(T oldObj, T newObj, String... ignores) {
List<Map<String, String>> list = new ArrayList<>();
// 获取所有属性定义,并设置为允许访问
Map<String, Field> fieldMap = getFieldMap(checkAndGetClass(oldObj, newObj));
// 遍历 map,比较2个属性值
Set<String> ignoreSet = getIgnoreSet(ignores);
fieldMap.forEach((k, v) -> {
if (!ignoreSet.contains(k)) {
try {
Object newVal = v.get(newObj);
if (Objects.nonNull(newVal)) {
Object oldVal = v.get(oldObj);
if (newObj != oldObj) {
HashMap<String, String> map = new HashMap<>();
map.put("field", k);
map.put("after", obj2str(newVal));
map.put("before", obj2str(oldVal));
list.add(map);
}
}
} catch (IllegalAccessException e) {
log.warn("反射异常", e);
throw MyException.error("反射异常");
}
}
});
return list;
}
/**
* Object 转成 String 格式
*/
private static String obj2str(Object obj) {
String str;
if (Objects.isNull(obj)) {
str = null;
} else if (obj instanceof LocalDate) {
str = DateUtils.time2yyyy_MM_dd((TemporalAccessor) obj);
} else if (obj instanceof LocalDateTime) {
str = DateUtils.time2yyyy_MM_dd_HH_mm_ss((TemporalAccessor) obj);
} else {
str = obj.toString();
}
return str;
}
/**
* 获取最终忽略属性的集合
*/
private static Set<String> getIgnoreSet(String... ignores) {
Set<String> ignoreSet = CollUtil.newHashSet(ignores);
ignoreSet.addAll(defaultIgnoreSet);
return ignoreSet;
}
/**
* 基本校验,获取 class
*/
private static Class<?> checkAndGetClass(Object oldObj, Object newObj) {
if (Objects.isNull(oldObj) || Objects.isNull(newObj)) {
throw MyException.error("比较差异的对象不能为空");
}
if (oldObj.getClass() != newObj.getClass()) {
throw MyException.error("比较差异的对象类型必须相同");
}
return oldObj.getClass();
}
/**
* 将类的所有 field,存入到 Map
*/
private static Map<String, Field> getFieldMap(Class<?> cls) {
return fieldMapCache.computeIfAbsent(cls.getName(), (key) -> {
Map<String, Field> clsFieldMap = new HashMap<>();
for (Field field : cls.getDeclaredFields()) {
field.setAccessible(true);
clsFieldMap.put(field.getName(), field);
}
return clsFieldMap;
});
}
public static void main(String[] args) {
Task task1 = new Task();
task1.setId(1);
task1.setName("任务1");
task1.setPlanStartTime(LocalDateTime.now());
task1.setEnable(true);
Task task2 = new Task();
task2.setId(2);
task2.setName("任务2");
task2.setPlanCompleteTime(LocalDateTime.now());
task2.setEnable(false);
log.info("对比差异:{}", diff(task1, task2, "create_time", "create_by"));
}
}
测试结果
12:33:22.782 [main] INFO com.gly.memo.utils.ObjectUtils - 对比差异:[{field=enable, before=true, after=false}, {field=name, before=任务1, after=任务2}, {field=id, before=1, after=2}, {field=planCompleteTime, before=null, after=2022-11-08 12:33:22}]