【工作中问题解决实践 六】基于反射及类装饰模式的数据对比框架(下)

简介: 【工作中问题解决实践 六】基于反射及类装饰模式的数据对比框架(下)

3 数据对比框架

重头戏数据对比框架实现,直接传入要对比的数据实体和规则实现类即可:

1 对比框架方法及返回结果定义

对比框架的基本方法和返回结果定义如下:

/**
 * * @Name DateCompareFramework
 * * @Description
 * * @author tianmaolin
 * * @Date 2022/9/7
 */
@NoArgsConstructor
@Service
public class DataCompareFramework {
    /**
     * 同一数据对比
     *
     * @return the compare entity result
     */
    public static <T> CompareResult<T> doCompare(List<T> originEntity, List<T> standardEntity, DataCompare<T> compareRule) {
        List<DataCompare<T>> originCompares = compareRule.toCompareList(originEntity);
        List<DataCompare<T>> standardCompares = compareRule.toCompareList(standardEntity);
        Set<String> originUniqueSet = originCompares.stream().map(DataCompare::getUniqueKey).collect(Collectors.toSet());
        Set<String> standardUniqueSet = standardCompares.stream().map(DataCompare::getUniqueKey).collect(Collectors.toSet());
        CompareResult<T> compareResult = new CompareResult<>();
        // 1. 三方标准数据较当前数据多的
        List<T> needAddList = standardCompares.stream().filter(c -> !originUniqueSet.contains(c.getUniqueKey())).map(DataCompare::getData).collect(Collectors.toList());
        compareResult.setNeedAddEntityList(needAddList);
        // 2. 三方标准数据较当前数据少的
        List<T> needDeleteList = originCompares.stream().filter(c -> !standardUniqueSet.contains(c.getUniqueKey())).map(DataCompare::getData).collect(Collectors.toList());
        compareResult.setNeedDeleteEntityList(needDeleteList);
        // 3. 当前数据与三方标准数据交集的数据
        List<DataCompare<T>> intersectionList = originCompares.stream().filter(c -> standardUniqueSet.contains(c.getUniqueKey())).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(intersectionList)) {
            Map<Long, List<ObjectDiffUtil.ModifiedPropertyInfo>> diffInfos = new HashMap<>(64);
            intersectionList.forEach(x -> {
                DataCompare<T> standardDate = standardCompares.stream().filter(y -> y.getUniqueKey().equals(x.getUniqueKey())).findFirst().orElse(null);
                if (Objects.isNull(standardDate)) {
                    return;
                }
                List<ObjectDiffUtil.ModifiedPropertyInfo> diffInfo = ObjectDiffUtil.getDifferentProperty(x.getData(), standardDate.getData(), x.getComparePropertyConfig());
                if (CollectionUtils.isEmpty(diffInfo)) {
                    return;
                }
                diffInfos.put(x.getIdByUniqueKey(), diffInfo);
            });
            compareResult.setIntersectionDiffInfo(diffInfos);
        }
        return compareResult;
    }
    /**
     * 数据对比结果,从当前数据集合视角出发
     *
     * @param <T> the type parameter
     */
    @Data
    @NoArgsConstructor
    public static class CompareResult<T> {
        /**
         * 当前数据较三方标准数据多的,待删除数据集合
         */
        private List<T> needDeleteEntityList = Lists.newArrayList();
        /**
         * 当前数据较三方标准数据少的,待补充数据集合
         */
        private List<T> needAddEntityList = Lists.newArrayList();
        /**
         * 交集数据属性对比
         */
        private Map<Long, List<ObjectDiffUtil.ModifiedPropertyInfo>> intersectionDiffInfo = new HashMap<>();
        /**
         * 交集数据中当前数据缺失关键属性
         */
        private List<T> lackInfoEntityList = Lists.newArrayList();
    }
}

2 反射实现交集数据对比

这里用到的交集数据对比方法实现如下,主要通过反射拿到实体所有属性和值,然后进行遍历比较:

/**
 * * @Name ObjectDiffUtil
 * * @Description
 * * @author tianmaolin
 * * @Date 2022/9/6
 */
@Slf4j
public class ObjectDiffUtil {
    /**
     * 比较两个对象属性值是否相同
     * 如果不同返回修改过的属性信息
     *
     * @param <T>    the type parameter
     * @param oldObj the old obj
     * @param newObj the new obj
     * @return 修改过的属性字段 different property
     */
    public static <T> List<ModifiedPropertyInfo> getDifferentProperty(T oldObj, T newObj, CompareProperty compareProperty) {
        // 1 校验是否需要比较
        if (Objects.isNull(oldObj) || Objects.isNull(newObj) || oldObj.equals(newObj)) {
            return Collections.emptyList();
        }
        // 2 准备比较数据
        List<PropertyModelInfo> oldObjectPropertyValue = getObjectPropertyValue(oldObj, compareProperty);
        if (CollectionUtils.isEmpty(oldObjectPropertyValue)) {
            return Collections.emptyList();
        }
        List<ModifiedPropertyInfo> modifiedPropertyInfos = new ArrayList<>(oldObjectPropertyValue.size());
        List<PropertyModelInfo> newObjectPropertyValue = getObjectPropertyValue(newObj, compareProperty);
        Map<String, Object> objectMap = new HashMap<>(newObjectPropertyValue.size());
        for (PropertyModelInfo propertyModelInfo : newObjectPropertyValue) {
            String propertyName = propertyModelInfo.getPropertyName();
            Object value = propertyModelInfo.getValue();
            objectMap.put(propertyName, value);
        }
        // 3 比较并返回比较结果
        for (PropertyModelInfo propertyModelInfo : oldObjectPropertyValue) {
            String propertyName = propertyModelInfo.getPropertyName();
            Object value = propertyModelInfo.getValue();
            if (objectMap.containsKey(propertyName)) {
                Object newValue = objectMap.get(propertyName);
                ModifiedPropertyInfo modifiedPropertyInfo = new ModifiedPropertyInfo();
                if (isObjectEmpty(value) && isObjectEmpty(newValue)) {
                    continue;
                }
                boolean notEqual = false;
                if (value instanceof BigDecimal) {
                    if (((BigDecimal) value).compareTo((BigDecimal) newValue) != 0) {
                        notEqual = true;
                    }
                } else if (!value.equals(newValue)) {
                    notEqual = true;
                }
                if (notEqual) {
                    modifiedPropertyInfo.setPropertyName(propertyName);
                    modifiedPropertyInfo.setOriginValue(value);
                    modifiedPropertyInfo.setStandardValue(newValue);
                    modifiedPropertyInfos.add(modifiedPropertyInfo);
                }
            }
        }
        return modifiedPropertyInfos;
    }
    /**
     * 通过反射获取对象的属性名称、getter返回值类型、属性值等信息
     *
     * @param <T> the type parameter
     * @param obj the obj
     * @return object property value
     */
    private static <T> List<PropertyModelInfo> getObjectPropertyValue(T obj, CompareProperty compareProperty) {
        if (Objects.isNull(obj)) {
            return Collections.emptyList();
        }
        Class<?> objClass = obj.getClass();
        PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(objClass);
        List<PropertyModelInfo> modelInfos = new ArrayList<>(propertyDescriptors.length);
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            Method readMethod = propertyDescriptor.getReadMethod();
            String name = propertyDescriptor.getName();
            boolean isNeedDiffProperty = false;
            // 如果对比属性配置类型为忽视,则不在忽视列表该属性参与对比
            if (PropertyConfigTypeEnum.IGNORE.getCode().equals(compareProperty.getType())) {
                isNeedDiffProperty = (readMethod != null && (CollectionUtils.isEmpty(compareProperty.getCompareProperty()) || !compareProperty.getCompareProperty().contains(name)));
            }
            // 如果对比属性配置类型为关注,则在关注列表该属性才参与对比
            if (PropertyConfigTypeEnum.FOCUS.getCode().equals(compareProperty.getType())) {
                isNeedDiffProperty = (readMethod != null && !CollectionUtils.isEmpty(compareProperty.getCompareProperty()) && compareProperty.getCompareProperty().contains(name));
            }
            if (isNeedDiffProperty) {
                Object invoke;
                Class<?> returnType = readMethod.getReturnType();
                try {
                    invoke = readMethod.invoke(obj);
                    PropertyModelInfo propertyModelInfo = new PropertyModelInfo();
                    propertyModelInfo.setPropertyName(name);
                    propertyModelInfo.setValue(invoke);
                    propertyModelInfo.setReturnType(returnType);
                    modelInfos.add(propertyModelInfo);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    LOGGER.error("反射获取类【" + objClass.getName() + "】方法异常,", e);
                }
            }
        }
        return modelInfos;
    }
    /**
     * 判断Object对象为空或空字符串
     *
     * @param obj
     * @return
     */
    public static Boolean isObjectEmpty(Object obj) {
        String str = ObjectUtils.toString(obj, "");
        return StringUtils.isBlank(str);
    }
    /**
     * 返回差异数据
     */
    @Data
    public static class ModifiedPropertyInfo implements Serializable {
        /**
         * 属性名称
         */
        private String propertyName;
        /**
         * 原始值
         */
        private Object originValue;
        /**
         * 标准值
         */
        private Object standardValue;
    }
    /**
     * 属性字段模型
     */
    @Data
    public static class PropertyModelInfo {
        /**
         * 属性名
         */
        private String propertyName;
        /**
         * 属性值
         */
        private Object value;
        /**
         * 类型
         */
        private Class<?> returnType;
    }
}

4 方法调用

方法调用非常简单,所有逻辑通过配置方式实现,通用实现逻辑内部封装:

public DataCompareFramework.CompareResult<CompanyShareholderEntity> comShareHolderInfoDiffByTyc(String comTaxNo) {
        List<CompanyShareholderEntity> companyShareholderEntities = companyShareHolderRepo.getByCompanyTaxNo(comTaxNo);
        if (CollectionUtils.isEmpty(companyShareholderEntities)) {
            throw new ServiceException("当前系统中不存在该公司下股东信息,comTaxNo:{}", comTaxNo);
        }
        List<CompanyShareholderEntity> tycShareholderEntities = companySyncService.getShareHolderEntityByTyc(comTaxNo);
        if (CollectionUtils.isEmpty(tycShareholderEntities)) {
            throw new ServiceException("天眼查不存在该公司下股东信息,comTaxNo:{}", comTaxNo);
        }
        return DataCompareFramework.doCompare(companyShareholderEntities, tycShareholderEntities, shareHolderDateCompare);
    }

总结一下

当前这套框架其实可以基于相同实体类型的数据集合进行对比,包括多的、少的、交的。如需新增对比,只需要新实现一个规则器就行了。但是还有问题尚待解决就是:

  1. 数据获取需要自己获取,后续可以通过配置数据源、配置脚本段的方式来获取数据
  2. 数据获取到需要转换为同一实体,这部分代码也有开发量,未来想着通过配置实现,指定字段映射,然后将映射后的json转为对象实体,再走后续的通用流程
  3. 数据对比结果拿到后后续其实还可以配置处理策略,例如newValue覆盖oldValue、舍弃newValue或是其它,拿到数据最好还是能处理一下

目前这个框架其实也不能称之为框架,只能定制化的解决当前的问题,只不过代码写的扩展度较高,后续随着业务的需要和发展迭代演进吧,再回顾下三年前那篇通过动态JsonSchem进行数据比对的文章,其实有相似之处,定义JsonSchem其实就是统一设计一个通用实体类,定制的节点规则其实就可以设计在增强的实体规则类里,

  • Schema的好处可能是新增时候除非具体比较规则变更(equal类似),否则代码不会变更,只需将适配新比较实体的JsonSchem写入数据库或配置文件,而对比框架则新增数据Json时需要配置实体和实体规则器,不过实体规则器配置化或者持久化后也貌似一个效果。
  • 对比框架的好处是不需要引入Schema这样一个复杂的概念,也不需要构造字典树,直接通过实体对象的反射功能就能轻松匹配数据,而Schema则需要构造字典数,不过好在代码只写一遍即可。而且对比框架可以输出差集,不仅仅是一对一的交集对比。

总的来说,感觉二者相似度也很高,只要对比框架再配置化一些就能抹平Schema的优势,而Schema也就是字典树构建又较框架反射实体麻烦,但好在构建代码只需写一遍(而且我也实现了),而Schema的差集结果输出也可以再通过制定字典树关键属性区分,也就是再优化代码的事儿。不相伯仲吧,实现思路还是类似的,都是对同一类格式的数据(无论是对象还是Json串)配置规则,只不过规则配置方式不同,匹配实现方式不同而已。

相关文章
|
7月前
|
安全 Java 数据安全/隐私保护
|
7月前
|
设计模式
二十三种设计模式全面解析-装饰器模式-超越继承的灵活装扮
二十三种设计模式全面解析-装饰器模式-超越继承的灵活装扮
|
4月前
|
缓存 项目管理
类与类之间的协作模式问题之享元模式在工作中应用的问题如何解决
类与类之间的协作模式问题之享元模式在工作中应用的问题如何解决
|
6月前
|
Java Spring
JAVA注解:传统与现代的完美结合,你的代码值得拥有!
【6月更文挑战第29天】Java注解,作为连接传统与现代的编程工具,简化企业级应用开发,提升代码可读性和维护性。通过自定义注解如`@Loggable`,可以将行为(如日志记录)与方法实现分离,减少模板代码。使用AOP(如Spring)处理注解,实现行为拦截,增强代码灵活性和可扩展性。拥抱Java注解,让代码更现代、更高效!
67 16
|
5月前
|
安全 Java
打破常规!JAVA反射技术让你“动态”编程
【7月更文挑战第1天】Java反射技术是动态编程的利器,它揭示了类的内部信息,允许运行时操作对象、调用方法和创建实例。动态加载类、调用方法和创建对象是其常见应用场景,但需注意反射带来的性能损失、安全风险和代码可读性下降。在平衡灵活性与效率时谨慎使用。
52 0
|
7月前
|
C++
C++ 数据封装的方法,重点是其编程思想
在C++中,数据封装一般指的是将数据和操作这些数据的函数绑定在一起的程序设计方式。通常使用C++的类来实现
71 7
|
7月前
|
存储 Java
java反射——设计框架的灵魂
java反射——设计框架的灵魂
|
设计模式 Java
JAVA设计模式5:建造者模式,将对象的构建过程与其表示分离
JAVA设计模式5:建造者模式,将对象的构建过程与其表示分离
115 0
|
设计模式 Java
简化复杂系统:深入解析Java设计模式中的外观模式
在软件开发领域,设计模式是一套经过验证的最佳实践方法,用于解决各种常见问题。外观模式是一种结构型设计模式,其目标是为复杂子系统提供一个简单的接口。在本文中,我们将深入探讨外观模式的核心思想,以及它在Java中的实际应用。
89 0
|
存储 算法 Java
Java集合重点知识详解——优点以及内部继承关系
Java集合重点知识详解——优点以及内部继承关系
101 0