数据使用前后对比图表(对比数据前后变化细节)

前言

在开发的过程中,有时候需要对数据进行比对,来判断是否发生变化。如果一个字段一个字段比较,就太麻烦了。所以通过整合注解与反射的方式,实现一个通用的实体数据比较框架。

设计
  1. 使用注解,确定需要比较的属性。
  2. 反射获取属性与数据内容。
  3. 循环比较数据内容,并写入到结果中。
  4. 提供多种比较入参

总体结构如下:

数据使用前后对比图表(对比数据前后变化细节)(1)

正文1、定义注解1) 实体注解,确定实体名称

不是基本类型是,必须要有该注解

import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 功能描述: 属性实体标识 <br/> */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface PropertyEntity { /** 实体唯一标识 */ String value(); }

2) 主键注解,校验数据是否一致

import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 功能描述: 唯一标记,可以又多个,用于联合索引 <br/> */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) public @interface PropertyId { }

3) 属性描述注解

import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 功能描述: 属性描述 <br/> */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD}) public @interface PropertyField { /** 中文描述 */ String name() default ""; /** 排序字段,与@PropertyOrder可以同时使用,取两个最大的为主 */ float order() default 0.00F; }

4) 顺序注解

import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 功能描述: 属性排序 <br/> */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD}) public @interface PropertyOrder { /** 排序值 */ float value() default 0.00F; }

5) 排除注解,不进行比较

import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 功能描述: 属性忽略比较 <br/> */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD}) public @interface PropertyIgnore { }

6) 自定义比较器

如果有特殊比较方式,则自行定义比较器

import com.cah.project.compare.comparator.DefaultComparator; import com.cah.project.compare.comparator.IComparator; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 功能描述: 属性比较器,可以自定义 <br/> */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD}) public @interface PropertyComparator { /** 比较器,默认比较器 */ Class<? extends IComparator> compare() default DefaultComparator.class; }

2、自定义比较器1) 比较器接口

/** * 功能描述: 比较器接口 <br/> */ public interface IComparator<T> { /** * 功能描述: 对象比较 <br/> * * @param t1 对象1 * @param t2 对象2 * @return "int" 返回比较结果 0-相同;非0-不同 */ int compare(T t1, T t2); }

2) 默认比较器实现

import java.util.Date; import java.util.Objects; /** * 功能描述: 默认比较器 <br/> */ public class DefaultComparator implements IComparator<Object> { @Override public int compare(Object o1, Object o2) { // 同时为空,为相同 if(Objects.isNull(o1) && Objects.isNull(o2)) { return 0; } // 都不为空 if(!Objects.isNull(o2) && !Objects.isNull(o1)) { if(o1 instanceof Date) { return ((Date) o1).compareTo((Date) o2); } else { if(o1 == o2 || o1.equals(o2)) { return 0; } } return -1; } return -1; } }

3、异常类

import com.cah.project.compare.enums.ExceptionEnum; import lombok.AllArgsConstructor; /** * 功能描述: 比较异常类 <br/> */ @AllArgsConstructor public class CompareException extends RuntimeException { private final String code; private final String desc; public CompareException(ExceptionEnum ee) { this(ee.getCode(), ee.getDesc()); } public CompareException(ExceptionEnum ee, Object... args) { this(ee.getCode(), String.format(ee.getDesc(), args)); } }

4、枚举定义1) 变化类型:新增,修改,删除,无变化等四种情况

import lombok.AllArgsConstructor; import lombok.Getter; /** * 功能描述: 变化类型枚举 <br/> */ @Getter @AllArgsConstructor public enum ChangeTypeEnum { ADDED("1", "新增"), REMOVED("2", "删除"), MODIFIED("3", "修改"), UNCHANGED("4", "无变化"), ; private final String code; private final String desc; }

2) 模型类型枚举

import lombok.AllArgsConstructor; import lombok.Getter; /** * 功能描述: 模型类型 <br/> */ @Getter @AllArgsConstructor public enum ModelTypeEnum { ENTITY("Entity", "实体"), PROPERTY("Property", "基础属性"), ENTITY_PROPERTY("EntityProperty", "实体属性"), LIST_PROPERTY("ListProperty", "列表属性"), MAP_PROPERTY("MapProperty", "Map属性"), ; private final String code; private final String desc; }

3) 异常枚举

import lombok.AllArgsConstructor; import lombok.Getter; /** * 功能描述: 异常枚举 <br/> */ @Getter @AllArgsConstructor public enum ExceptionEnum { OVER_DEPTH("0", "数据结构深度超过指定范围"), INCONSISTENT_CLASS("1", "比较的对象类型不一致"), PROPERTY_ENTITY_NULL("2", "比较的对象必须拥有@PropertyEntity注解"), PROPERTY_ID_NULL("3", "比较的对象必须拥有@PropertyId注解"), PROPERTY_ID_TYPE("4", "对象%s的@PropertyId注解类型必须为String或Long"), PROPERTY_ID_VALUE_NULL("5", "对象%s属性%s的@PropertyId注解的值为空"), ; private final String code; private final String desc; }

4) 实体类型枚举

这里使用了枚举 单例的模式。这里为什么不使用策略枚举的原因,在 AnalyzeUtil中需要做属性类型的判断,不方便使用。

import com.cah.project.compare.process.IPropertyProcess; import com.cah.project.compare.process.impl.BaseTypeProcess; import com.cah.project.compare.process.impl.EntityTypeProcess; import com.cah.project.compare.process.impl.ListTypeProcess; import com.cah.project.compare.process.impl.MapTypeProcess; import lombok.AllArgsConstructor; import lombok.Getter; import java.util.List; import java.util.Map; /** * 功能描述: 实体类型枚举 <br/> */ @Getter @AllArgsConstructor public enum PropertyTypeEnum { BASE_TYPE("base", "基础数据类型(int/String/...)", new BaseTypeProcess()), LIST_TYPE(List.class.getTypeName(), "List", new ListTypeProcess()), MAP_TYPE(Map.class.getTypeName(), "Map", new MapTypeProcess()), ENTITY_OBJECT_TYPE("entityObject", "自定义实体对象", new EntityTypeProcess()), ; private final String typeName; private final String desc; // 处理器 private final IPropertyProcess process; }

5、处理器,与实体类型枚举一起使用1) 处理器接口

import com.cah.project.compare.enums.ChangeTypeEnum; import com.cah.project.compare.model.ChangeModel; import com.cah.project.compare.model.PropertyModel; /** * 功能描述: 实体过程 <br/> */ public interface IPropertyProcess { /** * 功能描述: 单个对象处理 <br/> * * @param pm 属性模型 * @param cte 变化类型 * @return "com.cah.project.compare.model.ChangeModel" */ ChangeModel process(PropertyModel pm, ChangeTypeEnum cte) throws InstantiationException, IllegalAccessException; /** * 功能描述: 两个对象处理 <br/> * * @param beforePm before属性模型 * @param afterPm after属性模型 * @return "com.cah.project.compare.model.ChangeModel" */ ChangeModel process(PropertyModel beforePm, PropertyModel afterPm) throws InstantiationException, IllegalAccessException; }

2) 处理器抽象类

将共有的方法封装在这里,方便各个真实处理器继承使用

import com.cah.project.compare.enums.ChangeTypeEnum; import com.cah.project.compare.enums.ModelTypeEnum; import com.cah.project.compare.model.ChangeModel; import com.cah.project.compare.model.PropertyModel; import java.util.Objects; public abstract class AbsProcess implements IPropertyProcess { protected abstract ModelTypeEnum getModelType(); /** * 功能描述: 获取基本的类型 <br/> * * @param pm 属性模型 * @param cte 变化类型 * @return "com.compare.model.ChangeModel" */ protected ChangeModel getBaseChangeModel(PropertyModel pm, ChangeTypeEnum cte) { ChangeModel cm = getBaseChangeModel(pm); cm.setChangeType(cte); if(!Objects.isNull(pm.getValue())) { cm.setTypeValue(pm.getValue().toString()); } // 删除的,说明before有,after没有 cm.setBefore(ChangeTypeEnum.REMOVED.equals(cte) ? pm.getValue() : null); // 新增的,说明after有,before没有 cm.setAfter(ChangeTypeEnum.ADDED.equals(cte) ? pm.getValue() : null); return cm; } /** * 功能描述: 获取基本的类型 <br/> * * @param beforePm 改变前属性模型 * @param afterPm 改变后属性模型 * @return "com.compare.model.ChangeModel" */ protected ChangeModel getBaseChangeModel(PropertyModel beforePm, PropertyModel afterPm) { ChangeModel cm = getBaseChangeModel(beforePm); // 删除的,说明before有,after没有 cm.setBefore(beforePm.getValue()); // 新增的,说明after有,before没有 cm.setAfter(afterPm.getValue()); return cm; } /** * 功能描述: 获取基本的类型 <br/> * * @param pm 属性模型 * @return "com.compare.model.ChangeModel" */ protected ChangeModel getBaseChangeModel(PropertyModel pm) { ChangeModel cm = new ChangeModel(); cm.setTypeName(pm.getName()); cm.setTypeComment(pm.getPropertyName()); cm.setModelType(getModelType()); return cm; } /** * 功能描述: 比较对象 <br/> * * @param cm 变化模型 * @param beforePm before * @param afterPm after */ protected void compareChangeType(ChangeModel cm, PropertyModel beforePm, PropertyModel afterPm) { if(beforePm.getComparator().compare(beforePm.getValue(), afterPm.getValue()) == 0) { cm.setChangeType(ChangeTypeEnum.UNCHANGED); } else { cm.setChangeType(ChangeTypeEnum.MODIFIED); } } }

3) 基本类型处理器

import com.cah.project.compare.enums.ChangeTypeEnum; import com.cah.project.compare.enums.ModelTypeEnum; import com.cah.project.compare.model.ChangeModel; import com.cah.project.compare.model.PropertyModel; import com.cah.project.compare.process.AbsProcess; /** * 功能描述: 基本类型处理 <br/> */ public class BaseTypeProcess extends AbsProcess { @Override protected ModelTypeEnum getModelType() { return ModelTypeEnum.PROPERTY; } @Override public ChangeModel process(PropertyModel pm, ChangeTypeEnum cte) { return getBaseChangeModel(pm, cte); } @Override public ChangeModel process(PropertyModel beforePm, PropertyModel afterPm) { ChangeModel cm = getBaseChangeModel(beforePm, afterPm); compareChangeType(cm, beforePm, afterPm); return cm; } }

4) 实体类型处理器

需要与 CompareHelper配合使用,可能存在实体套实体的情况,会产生递归。

import com.cah.project.compare.enums.ChangeTypeEnum; import com.cah.project.compare.enums.ModelTypeEnum; import com.cah.project.compare.model.ChangeModel; import com.cah.project.compare.model.PropertyModel; import com.cah.project.compare.process.AbsProcess; import com.cah.project.compare.util.CompareHelper; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * 功能描述: 自定义实体类型处理 <br/> */ public class EntityTypeProcess extends AbsProcess { @Override protected ModelTypeEnum getModelType() { return ModelTypeEnum.ENTITY_PROPERTY; } @Override public ChangeModel process(PropertyModel pm, ChangeTypeEnum cte) throws InstantiationException, IllegalAccessException { ChangeModel cm = getBaseChangeModel(pm, cte); // 设置子节点 if(!Objects.isNull(pm.getValue())) { List<ChangeModel> children = new ArrayList<>(); ChangeModel child = CompareHelper.assemblyChangeModelObj(pm.getValue(), cte); children.add(child); cm.setChildren(children); } return cm; } @Override public ChangeModel process(PropertyModel beforePm, PropertyModel afterPm) throws InstantiationException, IllegalAccessException { ChangeModel cm = getBaseChangeModel(beforePm, afterPm); // 如果两个都为空,则为没变化 if(beforePm.getValue() == null && afterPm.getValue() == null) { cm.setChangeType(ChangeTypeEnum.UNCHANGED); } // 如果before的children不为空,after的为空,则为删除 if(beforePm.getValue() != null && afterPm.getValue() == null) { cm.setChangeType(ChangeTypeEnum.REMOVED); List<ChangeModel> children = new ArrayList<>(); ChangeModel child = CompareHelper.assemblyChangeModelObj(beforePm.getValue(), ChangeTypeEnum.REMOVED); children.add(child); cm.setChildren(children); } // 如果before的children为空,after的不为空,则为新增 if(beforePm.getValue() == null && afterPm.getValue() != null) { cm.setChangeType(ChangeTypeEnum.ADDED); List<ChangeModel> children = new ArrayList<>(); ChangeModel child = CompareHelper.assemblyChangeModelObj(afterPm.getValue(), ChangeTypeEnum.ADDED); children.add(child); cm.setChildren(children); } // 如果两个都不为空,则重新调用比较 if(beforePm.getValue() != null && afterPm.getValue() != null) { if(beforePm.getComparator() != null) { compareChangeType(cm, beforePm, afterPm); } else { // 默认未变化 cm.setChangeType(ChangeTypeEnum.UNCHANGED); // 设置子节点 List<ChangeModel> children = new ArrayList<>(); ChangeModel child = CompareHelper.assemblyChangeModelObj(beforePm.getKey(), beforePm.getValue(), afterPm.getValue()); children.add(child); cm.setChildren(children); if(!children.isEmpty()) { // 根据子信息,重新设置变化类型 cm.setChangeType(CompareHelper.getChildrenChangeType(children)); } } } return cm; } }

5) List处理器

因为List中一般是对象实体,所以需要用到 实体类型处理器,还要使用解析工具AnalyzeUtil,也会产生递归调用。

import com.cah.project.compare.enums.ChangeTypeEnum; import com.cah.project.compare.enums.ModelTypeEnum; import com.cah.project.compare.model.ChangeModel; import com.cah.project.compare.model.PropertyModel; import com.cah.project.compare.process.AbsProcess; import com.cah.project.compare.util.AnalyzeUtil; import com.cah.project.compare.util.CompareHelper; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; /** * 功能描述: List类型处理 <br/> */ public class ListTypeProcess extends AbsProcess { @Override protected ModelTypeEnum getModelType() { return ModelTypeEnum.LIST_PROPERTY; } @Override public ChangeModel process(PropertyModel pm, ChangeTypeEnum cte) throws InstantiationException, IllegalAccessException { ChangeModel cm = getBaseChangeModel(pm, cte); if(!Objects.isNull(pm.getValue())) { // 设置子节点 List<ChangeModel> children = new ArrayList<>(); // 继续比较列表 Map<String, Object> stringObjectMap = AnalyzeUtil.toMap((List) pm.getValue()); CompareHelper.assemblyChangeModelList(children, ChangeTypeEnum.REMOVED.equals(cte) ? stringObjectMap : null, ChangeTypeEnum.ADDED.equals(cte) ? stringObjectMap : null); cm.setChildren(children); } return cm; } @Override public ChangeModel process(PropertyModel beforePm, PropertyModel afterPm) throws InstantiationException, IllegalAccessException { ChangeModel cm = getBaseChangeModel(beforePm, afterPm); // 如果两个都为空,则为没变化 if(beforePm.getValue() == null && afterPm.getValue() == null) { cm.setChangeType(ChangeTypeEnum.UNCHANGED); } // 如果before的children不为空,after的为空,则为删除 if(beforePm.getValue() != null && afterPm.getValue() == null) { cm.setChangeType(ChangeTypeEnum.REMOVED); // 设置子节点 List<ChangeModel> children = new ArrayList<>(); // 继续比较列表 Map<String, Object> stringObjectMap = AnalyzeUtil.toMap((List) beforePm.getValue()); CompareHelper.assemblyChangeModelList(children, stringObjectMap, null); cm.setChildren(children); } // 如果before的children为空,after的不为空,则为新增 if(beforePm.getValue() == null && afterPm.getValue() != null) { cm.setChangeType(ChangeTypeEnum.ADDED); // 设置子节点 List<ChangeModel> children = new ArrayList<>(); // 继续比较列表 Map<String, Object> stringObjectMap = AnalyzeUtil.toMap((List) afterPm.getValue()); CompareHelper.assemblyChangeModelList(children, null, stringObjectMap); cm.setChildren(children); } // 如果两个都不为空,则重新调用比较 if(beforePm.getValue() != null && afterPm.getValue() != null) { // 默认未变化 cm.setChangeType(ChangeTypeEnum.UNCHANGED); // 设置子节点 List<ChangeModel> children = new ArrayList<>(); // 继续比较列表 Map<String, Object> beforeObjMap = AnalyzeUtil.toMap((List) beforePm.getValue()); Map<String, Object> afterObjMap = AnalyzeUtil.toMap((List) afterPm.getValue()); CompareHelper.assemblyChangeModelList(children, beforeObjMap, afterObjMap); cm.setChildren(children); if(!children.isEmpty()) { // 根据子信息,重新设置变化类型 cm.setChangeType(CompareHelper.getChildrenChangeType(children)); } } return cm; } }

5) Map处理器

如果在设计模型的过程中使用到了Map,要考虑一下,是不是可以使用实体进行定义。所以这个处理器就自由发挥吧,没有开发。

import com.cah.project.compare.enums.ChangeTypeEnum; import com.cah.project.compare.enums.ModelTypeEnum; import com.cah.project.compare.model.ChangeModel; import com.cah.project.compare.model.PropertyModel; import com.cah.project.compare.process.AbsProcess; /** * 功能描述: Map类型处理 <br/> */ public class MapTypeProcess extends AbsProcess { @Override protected ModelTypeEnum getModelType() { return ModelTypeEnum.MAP_PROPERTY; } @Override public ChangeModel process(PropertyModel pm, ChangeTypeEnum cte) throws InstantiationException, IllegalAccessException { ChangeModel cm = getBaseChangeModel(pm, cte); // TODO ... return cm; } @Override public ChangeModel process(PropertyModel beforePm, PropertyModel afterPm) throws InstantiationException, IllegalAccessException { ChangeModel cm = getBaseChangeModel(beforePm, afterPm); // TODO ... return cm; } }

6、模型定义1) 实体解析模型

解析需要比较的实体,进行存储。

import com.cah.project.compare.comparator.IComparator; import com.cah.project.compare.enums.PropertyTypeEnum; import lombok.Data; import java.lang.reflect.Type; /** * 功能描述: 属性模型 <br/> */ @Data public class PropertyModel { /** 如果是object,则key,否则与value一致 */ private String key; /** 属性名 */ private String name; /** 属性值 */ private Object value; /** 设置属性描述 */ private String propertyName; /** 属性所属的类 */ private Class<?> declaring; /** 属性类型 */ private Type type; /** 属性类型枚举 */ private PropertyTypeEnum pte; /** 属性比较器 */ private IComparator comparator; /** 所属实体标识 */ private String propertyEntity; /** 排序 */ private float order; }

2) 变化模型

最终每条数据变化情况

import com.cah.project.compare.enums.ChangeTypeEnum; import com.cah.project.compare.enums.ModelTypeEnum; import lombok.Data; import java.util.List; /** * 功能描述: 改变模型 <br/> */ @Data public class ChangeModel { /** 类型名 */ private String typeName; /** 类型值 */ private String typeValue; /** 类型描述(如果为对象,则是PropertyEntity,如果是属性,则为PropertyName) */ private String typeComment; /** 变化类型 */ private ChangeTypeEnum changeType; /** 改变前的数据 */ private Object before; /** 改变后的数据 */ private Object after; /** 子节点 */ private List<ChangeModel> children; /** 模型类型 */ private ModelTypeEnum modelType; }

7、解析工具 AnalyzeUtil

对比较对象进行解析的工具类

import com.cah.project.compare.annotation.*; import com.cah.project.compare.comparator.DefaultComparator; import com.cah.project.compare.enums.ExceptionEnum; import com.cah.project.compare.enums.PropertyTypeEnum; import com.cah.project.compare.exception.CompareException; import com.cah.project.compare.model.PropertyModel; import java.lang.reflect.Field; import java.util.*; import java.util.function.BinaryOperator; import java.util.stream.Collectors; /** * 功能描述: 解析工具 <br/> */ public class AnalyzeUtil { /** Key连接符 */ private static final String KEY_LINK = "|_|"; /** * 功能描述: 将List转换成Map,key为List中对象的 唯一标识(@PropertyId注解) <br/> * * @param cs Collection集合 * @return "java.util.Map<java.lang.String,java.lang.Object>" */ public static Map<String, Object> toMap(Collection<?> cs) throws IllegalAccessException { Map<String, Object> map = new HashMap<>(); if(cs != null && !cs.isEmpty()) { for(Object o : cs) { // 如果是基本类型,则直接toString String key = isBaseType(o.getClass().getName()) ? o.toString() : getKey(o); map.put(key, o); } } return map; } /** * 功能描述: 将对象转成map,key为属性名,value为对象映射成的PropertyModel模型 <br/> * * @param o 对象 * @return "java.util.Map<java.lang.String,com.compare.model.PropertyModel>" */ public static Map<String, PropertyModel> toMap(Object o) throws IllegalAccessException, InstantiationException { if(Objects.isNull(o)) { return null; } // 获取全部字段 Field[] fields = o.getClass().getDeclaredFields(); List<PropertyModel> list = new ArrayList<>(fields.length); String propertyEntity = getPropertyEntity(o); String key = getKey(o); for (Field field : fields) { // 非忽略注解 if(!isIgnore(field)) { field.setAccessible(true); PropertyModel pm = new PropertyModel(); pm.setKey(key); // 设置属性信息 pm.setName(field.getName()); pm.setValue(field.get(o)); // 设置类型 pm.setType(field.getType()); // 设置属性类型枚举 if(isBaseType(pm.getType().getTypeName())) { pm.setPte(PropertyTypeEnum.BASE_TYPE); } else if(isList(pm.getType().getTypeName())) { pm.setPte(PropertyTypeEnum.LIST_TYPE); } else if(isMap(pm.getType().getTypeName())) { pm.setPte(PropertyTypeEnum.MAP_TYPE); } else { pm.setPte(PropertyTypeEnum.ENTITY_OBJECT_TYPE); } PropertyField propertyField = field.getAnnotation(PropertyField.class); if(propertyField != null) { pm.setPropertyName(propertyField.name()); pm.setOrder(propertyField.order()); } PropertyOrder propertyOrder = field.getAnnotation(PropertyOrder.class); if(propertyOrder != null) { // 两个注解都有,哪个大取哪个 pm.setOrder(Math.max(pm.getOrder(), propertyOrder.value())); } // 设置比较器 PropertyComparator pc = field.getAnnotation(PropertyComparator.class); if(pc != null) { pm.setComparator(pc.compare().newInstance()); } else { if(PropertyTypeEnum.BASE_TYPE.equals(pm.getPte())) { pm.setComparator(new DefaultComparator()); } } pm.setDeclaring(field.getDeclaringClass()); pm.setPropertyEntity(propertyEntity); list.add(pm); } } // 转成有序map return list.stream().sorted(Comparator.comparing(PropertyModel::getOrder)) .collect(Collectors.toMap(PropertyModel::getName, e -> e, throwingMerger(), LinkedHashMap::new)); } /** * 功能描述: 异常处理 <br/> * * @return "java.util.function.BinaryOperator<T>" */ private static <T> BinaryOperator<T> throwingMerger() { return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); }; } /** * 功能描述: 确定唯一标识数据 <br/> * * @param o 对象 * @return "java.lang.String" */ public static String getKey(Object o) throws IllegalAccessException { if(isBaseType(o.getClass().getName())) { return o.toString(); } // 获取全部字段 Field[] fields = o.getClass().getDeclaredFields(); StringBuilder sb = new StringBuilder(); for(Field field : fields) { // 非忽略注解 if(!isIgnore(field)) { // 获取@PropertyId注解 PropertyId id = field.getAnnotation(PropertyId.class); if(id != null) { // 校验是否基本类型 String typeName = field.getType().getTypeName(); checkPropertyIdType(typeName, o); field.setAccessible(true); if(field.get(o) == null || "".equals(field.get(o))) { throw new CompareException(ExceptionEnum.PROPERTY_ID_VALUE_NULL, o.getClass().getName(), field.getName()); } sb.append(field.get(o).toString()); sb.append(KEY_LINK); } } } if(sb.length() <= 0) { throw new CompareException(ExceptionEnum.PROPERTY_ID_NULL); } return sb.substring(0, sb.lastIndexOf(KEY_LINK)); } /** * 功能描述: 获取实体标识注解内容 <br/> * * @param o 对象 * @return "java.lang.String" */ public static String getPropertyEntity(Object o) { return getPropertyEntity(o.getClass()); } /** * 功能描述: 获取实体标识注解内容 <br/> * * @param clazz 类 * @return "java.lang.String" */ public static String getPropertyEntity(Class<?> clazz) { if(isBaseType(clazz.getName())) { return ""; } PropertyEntity pe = clazz.getAnnotation(PropertyEntity.class); if(pe == null) { // 不为基本类型时,必须要有PropertyEntity注解 throw new CompareException(ExceptionEnum.PROPERTY_ENTITY_NULL); } return pe.value(); } /** * 功能描述: 是忽略注解字段 <br/> * * @param field 字段 * @return "boolean" true-是;false-不是 */ private static boolean isIgnore(Field field) { return field.getAnnotation(PropertyIgnore.class) != null; } /** * 功能描述: 是否列表 <br/> * * @param typeName 类型名称 * @return "boolean" */ private static boolean isList(String typeName) { return List.class.getTypeName().equals(typeName); } /** * 功能描述: 是否Map <br/> * * @param typeName 类型名称 * @return "boolean" */ private static boolean isMap(String typeName) { return Map.class.getTypeName().equals(typeName); } /** * 功能描述: 是否基本类型 <br/> * * @param className 类 * @return "boolean" true-是;false-不是 */ private static boolean isBaseType(String className) { if(className.equals(Integer.class.getName()) || className.equals(int.class.getName()) || className.equals(Byte.class.getName()) || className.equals(byte.class.getName()) || className.equals(Long.class.getName()) || className.equals(long.class.getName()) || className.equals(Double.class.getName()) || className.equals(double.class.getName()) || className.equals(Float.class.getName()) || className.equals(float.class.getName()) || className.equals(Character.class.getName()) || className.equals(char.class.getName()) || className.equals(Short.class.getName()) || className.equals(short.class.getName()) || className.equals(java.math.BigDecimal.class.getName()) || className.equals(java.math.BigInteger.class.getName()) || className.equals(Boolean.class.getName()) || className.equals(boolean.class.getName()) || className.equals(String.class.getName())) { return true; } return false; } /** * 功能描述: 基本类型校验 <br/> */ private static void checkPropertyIdType(String typeName, Object o) { if(!"java.lang.String".equals(typeName) && !"java.lang.Long".equals(typeName) && !"java.lang.Integer".equals(typeName) && !"long".equals(typeName) && !"int".equals(typeName)) { throw new CompareException(ExceptionEnum.PROPERTY_ID_TYPE, o.getClass().getName()); } } }

8、数据比较核心类

唯一提供对外的方法为 compareProcess,接收两个数据列表。然后对数据进行比较,返回

import com.cah.project.compare.enums.ChangeTypeEnum; import com.cah.project.compare.enums.ModelTypeEnum; import com.cah.project.compare.model.ChangeModel; import com.cah.project.compare.model.PropertyModel; import java.util.*; import java.util.stream.Collectors; /** * 功能描述: 比较帮助类 <br/> */ public class CompareHelper { /** * 功能描述: 数据比较(提供给外部使用) <br/> */ public static void compareProcess(List<ChangeModel> changeList, Collection<?> before, Collection<?> after) throws IllegalAccessException, InstantiationException { if((before == null || before.isEmpty()) && (after == null || after.isEmpty())) { return; } Map<String, Object> afterObjMap = null, beforeObjMap = null; if(before == null || before.isEmpty()) { // 全部为新增 // 转换为Map<String, Object> key:主键值,value:对象信息 afterObjMap = AnalyzeUtil.toMap(after); } if(after == null || after.isEmpty()) { // 全部为删除 beforeObjMap = AnalyzeUtil.toMap(before); } if(before != null && !before.isEmpty() && after != null && !after.isEmpty()) { // 修改或者不变 beforeObjMap = AnalyzeUtil.toMap(before); afterObjMap = AnalyzeUtil.toMap(after); } // 组转成changeModel assemblyChangeModelList(changeList, beforeObjMap, afterObjMap); } /** * 功能描述: 组装 <br/> * * @param changeList 变化列表 * @param beforeObjMap before * @param afterObjMap after */ public static void assemblyChangeModelList(List<ChangeModel> changeList, Map<String, Object> beforeObjMap, Map<String, Object> afterObjMap) throws IllegalAccessException, InstantiationException { if(beforeObjMap == null) { Set<Map.Entry<String, Object>> entries = afterObjMap.entrySet(); for (Map.Entry<String, Object> entry : entries) { ChangeModel cm = assemblyChangeModelObj(entry.getValue(), ChangeTypeEnum.ADDED); changeList.add(cm); } return; } if(afterObjMap == null) { Set<Map.Entry<String, Object>> entries = beforeObjMap.entrySet(); for (Map.Entry<String, Object> entry : entries) { ChangeModel cm = assemblyChangeModelObj(entry.getValue(), ChangeTypeEnum.REMOVED); changeList.add(cm); } return; } Set<Map.Entry<String, Object>> beforeEntries = beforeObjMap.entrySet(); for(Map.Entry<String, Object> beforeEntry : beforeEntries) { ChangeModel cm; if (!afterObjMap.containsKey(beforeEntry.getKey())) { // 如果在after中不存在key,则为删除 cm = assemblyChangeModelObj(beforeEntry.getValue(), ChangeTypeEnum.REMOVED); } else { // 继续判断是不变,还是修改 cm = assemblyChangeModelObj(beforeEntry.getKey(), beforeEntry.getValue(), afterObjMap.get(beforeEntry.getKey())); } changeList.add(cm); } // 如果after中有before没有的值,则为新增 Set<Map.Entry<String, Object>> afterEntries = afterObjMap.entrySet(); for (Map.Entry<String, Object> afterEntry : afterEntries) { if(!beforeObjMap.containsKey(afterEntry.getKey())) { ChangeModel cm = assemblyChangeModelObj(afterEntry.getValue(), ChangeTypeEnum.ADDED); changeList.add(cm); } } } /** * 功能描述: 组转对象 <br/> * * @param key 改变前 * @param before 改变前 * @param after 改变后 * @return "com.compare.model.ChangeModel" */ public static ChangeModel assemblyChangeModelObj(String key, Object before, Object after) throws IllegalAccessException, InstantiationException { ChangeModel cm = new ChangeModel(); // 默认不变 cm.setChangeType(ChangeTypeEnum.UNCHANGED); cm.setModelType(ModelTypeEnum.ENTITY); cm.setBefore(before); cm.setAfter(after); // 设置数据主键 cm.setTypeValue(key); // 设置类名 cm.setTypeName(before.getClass().getSimpleName()); cm.setTypeComment(AnalyzeUtil.getPropertyEntity(before)); // 转换属性 Map<String, PropertyModel> beforeFiledMap = AnalyzeUtil.toMap(before); Map<String, PropertyModel> afterFiledMap = AnalyzeUtil.toMap(after); if(beforeFiledMap != null && afterFiledMap != null) { // 比较属性是否一致 List<ChangeModel> children = new ArrayList<>(); // 同一个对象下面,字段属性肯定一致,缩编去一个key就行 Set<Map.Entry<String, PropertyModel>> beforeEntries = beforeFiledMap.entrySet(); for (Map.Entry<String, PropertyModel> beforeEntry : beforeEntries) { // 获取属性key String propertyKey = beforeEntry.getKey(); // 通过属性类型,对比两个数据 ChangeModel child = beforeEntry.getValue().getPte().getProcess().process(beforeEntry.getValue(), afterFiledMap.get(propertyKey)); children.add(child); } cm.setChildren(children); } if(cm.getChildren() != null && !cm.getChildren().isEmpty()) { cm.setChangeType(getChildrenChangeType(cm.getChildren())); } return cm; } /** * 功能描述: 获取子属性的最终变化类型 <br/> * * @param children 子变化模型 * @return "com.compare.enums.ChangeTypeEnum" */ public static ChangeTypeEnum getChildrenChangeType(List<ChangeModel> children) { // 获取children中是否全部为的变化类型 List<ChangeTypeEnum> changeTypes = children.stream().map(ChangeModel::getChangeType).distinct().collect(Collectors.toList()); if(changeTypes.size() == 1 && ChangeTypeEnum.UNCHANGED.equals(changeTypes.get(0))) { return ChangeTypeEnum.UNCHANGED; } else { return ChangeTypeEnum.MODIFIED; } } /** * 功能描述: 组装单个对象 <br/> * * @param obj 对象 * @param cte 变化类型 * @return "com.compare.model.ChangeModel" */ public static ChangeModel assemblyChangeModelObj(Object obj, ChangeTypeEnum cte) throws IllegalAccessException, InstantiationException { ChangeModel cm = new ChangeModel(); // 设置变化类型 cm.setChangeType(cte); cm.setModelType(ModelTypeEnum.ENTITY); // 删除的,说明before有,after没有 cm.setBefore(ChangeTypeEnum.REMOVED.equals(cte) ? obj : null); // 新增的,说明after有,before没有 cm.setAfter(ChangeTypeEnum.ADDED.equals(cte) ? obj : null); // 设置数据主键 cm.setTypeValue(AnalyzeUtil.getKey(obj)); // 设置类名 cm.setTypeName(obj.getClass().getSimpleName()); cm.setTypeComment(AnalyzeUtil.getPropertyEntity(obj)); // 转换属性 Map<String, PropertyModel> filedMap = AnalyzeUtil.toMap(obj); if(filedMap != null) { List<ChangeModel> children = new ArrayList<>(); Set<Map.Entry<String, PropertyModel>> entries = filedMap.entrySet(); for (Map.Entry<String, PropertyModel> entry : entries) { // 拼接字段 ChangeModel cmc = entry.getValue().getPte().getProcess().process(entry.getValue(), cte); if(cmc != null) { children.add(cmc); } } cm.setChildren(children); } return cm; } }

9、提供对外调用类 CompareCore

所有需要对数据进行比较的,都调用该类的方法。

import com.cah.project.compare.enums.ExceptionEnum; import com.cah.project.compare.exception.CompareException; import com.cah.project.compare.model.ChangeModel; import com.cah.project.compare.util.CompareHelper; import java.util.*; /** * 功能描述: 比较核心方法 <br/> */ public class CompareCore { /** * 功能描述: 列表比较 <br/> * * @param before 之前 * @param after 之后 */ public static <E> List<ChangeModel> compare(Collection<E> before, Collection<E> after) throws IllegalAccessException, InstantiationException { List<ChangeModel> changeList = new ArrayList<>(); CompareHelper.compareProcess(changeList, before, after); return changeList; } /** * 功能描述: 对象比较 <br/> * * @param before 之前 * @param after 之后 * @return "java.util.List<com.cah.project.compare.model.ChangeModel>" */ public static List<ChangeModel> compare(Object before, Object after) throws IllegalAccessException, InstantiationException { // 校验 o1 和 o2 的类型是一致的 if(!before.getClass().equals(after.getClass())) { throw new CompareException(ExceptionEnum.INCONSISTENT_CLASS); } return compare(Collections.singletonList(before), Collections.singletonList(after)); } }

测试

import com.cah.project.compare.CompareCore; import com.cah.project.compare.model.ChangeModel; import com.cah.project.compare.test.entity.User; import java.util.ArrayList; import java.util.List; public class CompareTest { public static void main(String[] args) throws Exception { List<ChangeModel> compare = CompareCore.compare(getUserListLeft().get(1), getUserListRight().get(1)); System.out.println(compare); } private static List<User> getUserListLeft() { List<User> list = new ArrayList<>(); User user = new User(); user.setIdCard("11111"); user.setName("张三"); list.add(user); User user1 = new User(); user1.setIdCard("11112"); user1.setName("李四"); User user11 = new User(); user11.setIdCard("1122"); user11.setName("利佩欧"); user1.setSpouse(user11); List<User> children = new ArrayList<>(); User user111 = new User(); user111.setIdCard("11221"); user111.setName("利斯海1"); children.add(user111); user1.setChildren(children); User user112 = new User(); user112.setIdCard("11222"); user112.setName("利斯海2"); children.add(user112); user1.setChildren(children); list.add(user1); return list; } private static List<User> getUserListRight() { List<User> list = new ArrayList<>(); User user = new User(); user.setIdCard("11111"); user.setName("张三"); list.add(user); User user1 = new User(); user1.setIdCard("11112"); user1.setName("李四"); User user11 = new User(); user11.setIdCard("1122"); user11.setName("利佩欧"); user1.setSpouse(user11); List<User> children = new ArrayList<>(); User user111 = new User(); user111.setIdCard("11221"); user111.setName("利斯海1"); children.add(user111); user1.setChildren(children); User user112 = new User(); user112.setIdCard("11222"); user112.setName("利斯海22"); children.add(user112); user1.setChildren(children); list.add(user1); return list; }

结果

可以看出,只要子项有变化,主项的最终结论也是变化。

数据使用前后对比图表(对比数据前后变化细节)(2)

[ChangeModel(typeName=User, typeValue=11112, typeComment=用户信息, changeType=MODIFIED, before=User(idCard=11112, name=李四, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=User(idCard=1122, name=利佩欧, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=null, children=null), children=[User(idCard=11221, name=利斯海1, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=null, children=null), User(idCard=11222, name=利斯海2, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=null, children=null)]), after=User(idCard=11112, name=李四, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=User(idCard=1122, name=利佩欧, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=null, children=null), children=[User(idCard=11221, name=利斯海1, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=null, children=null), User(idCard=11222, name=利斯海22, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=null, children=null)]), children=[ChangeModel(typeName=idCard, typeValue=null, typeComment=身份证, changeType=UNCHANGED, before=11112, after=11112, children=null, modelType=PROPERTY), ChangeModel(typeName=name, typeValue=null, typeComment=姓名, changeType=UNCHANGED, before=李四, after=李四, children=null, modelType=PROPERTY), ChangeModel(typeName=age, typeValue=null, typeComment=年龄, changeType=UNCHANGED, before=null, after=null, children=null, modelType=PROPERTY), ChangeModel(typeName=stature, typeValue=null, typeComment=身高, changeType=UNCHANGED, before=0.0, after=0.0, children=null, modelType=PROPERTY), ChangeModel(typeName=weight, typeValue=null, typeComment=体重, changeType=UNCHANGED, before=null, after=null, children=null, modelType=PROPERTY), ChangeModel(typeName=gender, typeValue=null, typeComment=性别, changeType=UNCHANGED, before=0, after=0, children=null, modelType=PROPERTY), ChangeModel(typeName=totalDeposit, typeValue=null, typeComment=总存款, changeType=UNCHANGED, before=null, after=null, children=null, modelType=PROPERTY), ChangeModel(typeName=spouse, typeValue=null, typeComment=配偶, changeType=UNCHANGED, before=User(idCard=1122, name=利佩欧, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=null, children=null), after=User(idCard=1122, name=利佩欧, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=null, children=null), children=[ChangeModel(typeName=User, typeValue=11112, typeComment=用户信息, changeType=UNCHANGED, before=User(idCard=1122, name=利佩欧, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=null, children=null), after=User(idCard=1122, name=利佩欧, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=null, children=null), children=[ChangeModel(typeName=idCard, typeValue=null, typeComment=身份证, changeType=UNCHANGED, before=1122, after=1122, children=null, modelType=PROPERTY), ChangeModel(typeName=name, typeValue=null, typeComment=姓名, changeType=UNCHANGED, before=利佩欧, after=利佩欧, children=null, modelType=PROPERTY), ChangeModel(typeName=age, typeValue=null, typeComment=年龄, changeType=UNCHANGED, before=null, after=null, children=null, modelType=PROPERTY), ChangeModel(typeName=stature, typeValue=null, typeComment=身高, changeType=UNCHANGED, before=0.0, after=0.0, children=null, modelType=PROPERTY), ChangeModel(typeName=weight, typeValue=null, typeComment=体重, changeType=UNCHANGED, before=null, after=null, children=null, modelType=PROPERTY), ChangeModel(typeName=gender, typeValue=null, typeComment=性别, changeType=UNCHANGED, before=0, after=0, children=null, modelType=PROPERTY), ChangeModel(typeName=totalDeposit, typeValue=null, typeComment=总存款, changeType=UNCHANGED, before=null, after=null, children=null, modelType=PROPERTY), ChangeModel(typeName=spouse, typeValue=null, typeComment=配偶, changeType=UNCHANGED, before=null, after=null, children=null, modelType=ENTITY_PROPERTY), ChangeModel(typeName=children, typeValue=null, typeComment=孩子们, changeType=UNCHANGED, before=null, after=null, children=null, modelType=LIST_PROPERTY)], modelType=ENTITY)], modelType=ENTITY_PROPERTY), ChangeModel(typeName=children, typeValue=null, typeComment=孩子们, changeType=MODIFIED, before=[User(idCard=11221, name=利斯海1, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=null, children=null), User(idCard=11222, name=利斯海2, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=null, children=null)], after=[User(idCard=11221, name=利斯海1, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=null, children=null), User(idCard=11222, name=利斯海22, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=null, children=null)], children=[ChangeModel(typeName=User, typeValue=11221, typeComment=用户信息, changeType=UNCHANGED, before=User(idCard=11221, name=利斯海1, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=null, children=null), after=User(idCard=11221, name=利斯海1, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=null, children=null), children=[ChangeModel(typeName=idCard, typeValue=null, typeComment=身份证, changeType=UNCHANGED, before=11221, after=11221, children=null, modelType=PROPERTY), ChangeModel(typeName=name, typeValue=null, typeComment=姓名, changeType=UNCHANGED, before=利斯海1, after=利斯海1, children=null, modelType=PROPERTY), ChangeModel(typeName=age, typeValue=null, typeComment=年龄, changeType=UNCHANGED, before=null, after=null, children=null, modelType=PROPERTY), ChangeModel(typeName=stature, typeValue=null, typeComment=身高, changeType=UNCHANGED, before=0.0, after=0.0, children=null, modelType=PROPERTY), ChangeModel(typeName=weight, typeValue=null, typeComment=体重, changeType=UNCHANGED, before=null, after=null, children=null, modelType=PROPERTY), ChangeModel(typeName=gender, typeValue=null, typeComment=性别, changeType=UNCHANGED, before=0, after=0, children=null, modelType=PROPERTY), ChangeModel(typeName=totalDeposit, typeValue=null, typeComment=总存款, changeType=UNCHANGED, before=null, after=null, children=null, modelType=PROPERTY), ChangeModel(typeName=spouse, typeValue=null, typeComment=配偶, changeType=UNCHANGED, before=null, after=null, children=null, modelType=ENTITY_PROPERTY), ChangeModel(typeName=children, typeValue=null, typeComment=孩子们, changeType=UNCHANGED, before=null, after=null, children=null, modelType=LIST_PROPERTY)], modelType=ENTITY), ChangeModel(typeName=User, typeValue=11222, typeComment=用户信息, changeType=MODIFIED, before=User(idCard=11222, name=利斯海2, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=null, children=null), after=User(idCard=11222, name=利斯海22, age=null, weight=null, stature=0.0, gender=0, totalDeposit=null, spouse=null, children=null), children=[ChangeModel(typeName=idCard, typeValue=null, typeComment=身份证, changeType=UNCHANGED, before=11222, after=11222, children=null, modelType=PROPERTY), ChangeModel(typeName=name, typeValue=null, typeComment=姓名, changeType=MODIFIED, before=利斯海2, after=利斯海22, children=null, modelType=PROPERTY), ChangeModel(typeName=age, typeValue=null, typeComment=年龄, changeType=UNCHANGED, before=null, after=null, children=null, modelType=PROPERTY), ChangeModel(typeName=stature, typeValue=null, typeComment=身高, changeType=UNCHANGED, before=0.0, after=0.0, children=null, modelType=PROPERTY), ChangeModel(typeName=weight, typeValue=null, typeComment=体重, changeType=UNCHANGED, before=null, after=null, children=null, modelType=PROPERTY), ChangeModel(typeName=gender, typeValue=null, typeComment=性别, changeType=UNCHANGED, before=0, after=0, children=null, modelType=PROPERTY), ChangeModel(typeName=totalDeposit, typeValue=null, typeComment=总存款, changeType=UNCHANGED, before=null, after=null, children=null, modelType=PROPERTY), ChangeModel(typeName=spouse, typeValue=null, typeComment=配偶, changeType=UNCHANGED, before=null, after=null, children=null, modelType=ENTITY_PROPERTY), ChangeModel(typeName=children, typeValue=null, typeComment=孩子们, changeType=UNCHANGED, before=null, after=null, children=null, modelType=LIST_PROPERTY)], modelType=ENTITY)], modelType=LIST_PROPERTY)], modelType=ENTITY)]

总结

项目开发过程中,用到的一个小工具,性能问题,有待优化。后续看看能不能再精简一下配置内容与命名。

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页