前言
在开发的过程中,有时候需要对数据进行比对,来判断是否发生变化。如果一个字段一个字段比较,就太麻烦了。所以通过整合注解与反射的方式,实现一个通用的实体数据比较框架。
设计
- 使用注解,确定需要比较的属性。
- 反射获取属性与数据内容。
- 循环比较数据内容,并写入到结果中。
- 提供多种比较入参
总体结构如下:
正文
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;
}
复制代码
结果
可以看出,只要子项有变化,主项的最终结论也是变化。
[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)]
复制代码
代码地址
entity-compare-api
总结
项目开发过程中,用到的一个小工具,性能问题,有待优化。后续看看能不能再精简一下配置内容与命名。