数据比较器,对比数据前后变化细节

news2024/11/26 9:41:57

前言

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

设计

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

总体结构如下:

正文

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

总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/68339.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

进程管理笔记

查看进程详情 命令&#xff1a;ps -aux 查看进程 能够观察所有系统的数据 命令&#xff1a;ps la | head -5 命令&#xff1a;ps axjf | head -20 仅查看自己的bash相关的进程 命令&#xff1a;ps l 观察系统所有进程 命令&#xff1a;ps aux 观察进程变化命令 - top …

微服务框架 SpringCloud微服务架构 21 RestClient 操作文档 21.3 更新文档

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构21 RestClient 操作文档21.3 更新文档21.3.1 更新文档21 RestClient 操作文…

Python:函数进阶

目录 一、Python中的推导式 需求一 需求二 二、Python的全局作用域 三、Python的多参数传递 四、Python的装饰器 被装饰的方法不带参数 被装饰的方法带参数 带参数的装饰器 一、Python中的推导式 列表生成式是python内置的一种创建列表的方法&#xff0c;通过在[ ]内部执…

会话跟踪技术(Cookie和Session)

目录概述Cookie基本使用Cookie原理Cookie 存活时间Session基本使用Session原理Session使用细节Seesion 销毁&#xff1a;Cookie和Session的对比最后概述 会话&#xff1a; 用户打开浏览器&#xff0c;访问web服务器的资源&#xff0c;会话建立&#xff0c;直到有一方断开连接&…

Mac系统安装Kafka 3.x及可视化工具

写在前面&#xff1a;博主是一只经过实战开发历练后投身培训事业的“小山猪”&#xff0c;昵称取自动画片《狮子王》中的“彭彭”&#xff0c;总是以乐观、积极的心态对待周边的事物。本人的技术路线从Java全栈工程师一路奔向大数据开发、数据挖掘领域&#xff0c;如今终有小成…

网络工程师备考3章

注&#xff1a;本章考察较少&#xff0c;冲刺阶段可直接跳过 最常考点&#xff1a;帧中继&#xff0c;HDLC 3.1 公共交换电话网 英文&#xff1a;Public Switched Telephone Network ,PSTN 这种主网架构已经被淘汰了&#xff0c;现在的电话骨干网都是数字信号&#xff0c;目…

web课程设计网页规划与设计 基于HTML+CSS+JavaScript制作智能停车系统公司网站静态模板

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

Java中三种I/O模型 BIO,NIO,AIO

UNIX 系统下&#xff0c; IO 模型一共有 5 种&#xff1a; 同步阻塞 I/O、同步非阻塞 I/O、I/O 多路复用、信号驱动 I/O 和异步 I/O。 这也是我们经常提到的 5 种 IO 模型 &#xff08;1&#xff09;同步阻塞I/O模型 应用程序发起read调用后&#xff0c;一直处于阻塞状态 内…

BP综述:自闭症中基于功能连接体的预测模型

自闭症是一种异质性的神经发育疾病&#xff0c;基于功能磁共振成像的研究有助于推进我们对其对大脑网络活动影响的理解。我们回顾了使用功能连接和症状的测量的预测建模如何帮助揭示对这种情况的关键见解。我们讨论了不同的预测框架如何进一步加深我们对复杂自闭症症状学基础的…

Word处理控件Aspose.Words功能演示:在 Python 中将 Word DOCX 或 DOC 转换为 PDF

Word 到PDF是最流行和执行最广泛的文档转换之一。DOCX或DOC文件在打印或共享之前会转换为 PDF 格式。在本文中&#xff0c;我们将在 Python 中自动将 Word 转换为 PDF。步骤和代码示例将演示如何使用 Python 将 Word DOCX 或 DOC 转换为 PDF。此外&#xff0c;您将了解自定义 W…

Stable Diffusion模型阅读笔记

Stable Diffusion模型 什么是Stable Diffusion模型 一般而言&#xff0c;扩散是在图像中反复添加小且随机的噪声。与之相反&#xff0c;Stable Diffusion模型是一种将噪声生成为图像的机器学习模型。经过训练&#xff0c;它可逐步对随机高斯噪声进行去噪以获得感兴趣的样本&a…

Apache Hop Transforms Samples【持续完善中】

Samples transforms 1、abort-basic.hpl 根据筛选器行转换的结果中止此管道 第一步:添加Data grid转换,Meta部分维护字段。 Data部分维护数据,如下图。 第二步:添加filter rows,如下图 按照如下截图进行修改: 第三步:添加Abort 第四步:执行截图如下:

[附源码]Python计算机毕业设计Django学生综合数据分析系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

Golang 【basic_leaming】fmt.Print, fmt.Printf, fmt.Println 的区别

阅读目录fmt 包fmt.Print 原样输出fmt.Printf 格式输出fmt.Println 值 空格 输出换行输出Println 和 Print 区别Println 和 Printf 区别变量类型推导方式定义变量使用 Printf打印一个变量的类型fmt 包 fmt format&#xff0c;是一种格式化输出函数汇总包&#xff0c;用于格式…

这款国产API工具也太强了吧!让我放弃了postman

为什么弃用postman转用Eolink? 程序员在接口开发完成后都通常需要自测&#xff0c;当返回结果根据符合预期时&#xff0c;则表示代表接口可用。自己以前用的是postman来进行接口测试&#xff0c;但postman只能进行接口测试&#xff0c;有局限性、很多场景不能满足我的需求&am…

jenkins集成sonarqube

一、在linux上面安装sonarqube 相关文件下载地址 &#xff08;1&#xff09;下载sonarqube-9.6.1.59531.zip解压 &#xff08;2&#xff09;创建用户 useradd sonar passwd sonar(3)赋权 chown -R sonar.sonar /opt/sonarqube/sonarqube-9.6.1.59531(4)切换用户&#xff0c…

【C++】C++11新特性

目录 一.列表初始化initializer_list 1.什么是列表初始化 2.列表初始化的原理 二.auto/decltype/nullptr 1.auto - 自动类型推导 2.decltype - 指定类型 3.nullptr - C空指针 三.范围for 四.右值引用/移动构造/移动赋值/万能引用/完美转发 1.什么是右值 2.左值与右值…

react面试题总结一波,以备不时之需

React组件的构造函数有什么作用&#xff1f;它是必须的吗&#xff1f; 构造函数主要用于两个目的&#xff1a; 通过将对象分配给this.state来初始化本地状态将事件处理程序方法绑定到实例上 所以&#xff0c;当在React class中需要设置state的初始值或者绑定事件时&#xff…

首版20年后开源反病毒引擎 ClamAV 1.0 发布

导读ClamAV 是一个开源的&#xff08;GPL&#xff09;反病毒引擎&#xff0c;用于检测木马、病毒、恶意软件和其他恶意威胁。它为用户提供了许多实用程序&#xff0c;包括一个可扩展的多线程守护程序、一个命令行扫描器和一个自动更新数据库的高级工具。 ClamAV 是一个开源的&a…

rust编译器教我做人,为啥还要学习rust语言,因为想使用rust做一些底层服务,更深入的研究技术。

目录1&#xff0c;继续学习Rust语言&#xff0c;确实学习成本很高&#xff0c;学了两周还在学习入门概念&#xff0c;和编译器斗争2&#xff0c;rust学习曲线非常高&#xff0c;为啥还要坚持学习&#xff0c;一直想写一些服务研究研究底层的技术啥的3&#xff0c;rust对前端也有…