zoukankan      html  css  js  c++  java
  • Spring Boot 通用对象列表比较和去重

    1、前言

      在Excel批量导入数据时,常常需要与数据库中已存在数据的比较,并且需要考虑导入数据重复的可能性。

      导入的行数据,一般有一个实体类(对象)与之对应,往往这个实体类在数据库中的字段要比导入数据更多,如主键ID字段,这个ID字段一般不会出现在导入行数据中,此时导入的对象使用其它的唯一键来识别,如导入某个单位的用户数据,用用户名或手机号来唯一识别用户,而不会有用户ID字段(此时新用户的用户ID还有待系统来分配)。

      批量导入数据,往往需要与已存在对象数据进行比较,新的对象使用insert操作,已有对象使用update操作。但如果逐条数据,都先查询数据库,再决定是insert,还是update,是非常低效的,代码也会很臃肿。使用类似Mysql的ON DUPLICATE KEY UPDATE ,是一种解决方案,但在使用全局ID时,可能存在ID主键与其它唯一主键冲突的情况,导致update失败。

      因此,稳妥的作法,是比较导入对象集与存在对象集,比较的结果产生了4个集合:新增数据集合、属性值完全相同的对象集合、对象键值相同但属性有变化的对象集合、剩余对象集合。

      一般批量导入数据,会有一个限定,如导入用户数据,限定导入某一个单位的,这样比较的数据集就不会太大。

      因此,对象列表的比较,是批量数据导入的基础性功能。另外,导入数据,首先要消除数据中重复的对象,这些重复的对象数据是异常数据。

    2、解决方案

      对象列表比较,应支持通用实体类对象,这样可实现代码的复用,消除低效重复的针对具体实体类的代码。因此需要支持泛型,且使用反射机制。

      另外,对象比较,是一组属性字段值比较,包括键值与普通属性值,键值用于识别对象。

      代码如下:

    package com.abc.example.common.utils;
    
    import java.lang.reflect.Field;
    import java.util.List;
    import java.util.Map;
    
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * @className	        : ObjectListUtil
     * @description	        : 对象列表工具类
     * @summary		:
     * @history		:
     * ------------------------------------------------------------------------------
     * date		version		modifier		remarks                   
     * ------------------------------------------------------------------------------
     * 2021/08/15	1.0.0		sheng.zheng		初版
     *
     */
    @Slf4j
    public class ObjectListUtil {
        /**
         * 
         * @methodName	: compareTwoList
         * @description	: 比较两个对象列表,新列表与旧列表对比,根据比较的属性字段字典,比较结果
         * 			  1、新的对象;2、键值相同,属性不同,需要修改的对象;3、属性完全相同
         * 			  这样有新增对象列表,修改对象列表,相同对象列表,剩余对象列表
         * @param <T>	: 泛型类型T
         * @param fieldMap	: 比较字段字典,key为字段名称,value为是否键值,1表示键值字段,0为普通字段。
         * @param newList	: 新列表,要求键值无重复
         * @param oldList	: 旧列表,要求键值无重复,在方法中将被改变
         * @param addList	: 新增对象列表
         * @param sameList	: 相同对象列表
         * @param updateList: 修改对象列表
         * @param remainderList: 剩余对象列表
         * @history		:
         * ------------------------------------------------------------------------------
         * date		version		modifier		remarks                   
         * ------------------------------------------------------------------------------
         * 2021/08/15	1.0.0		sheng.zheng		初版
         *
         */
        public static <T> void compareTwoList(Map<String,Integer> fieldMap,
        	List<T> newList,List<T> oldList,
        	List<T> addList, List<T> sameList, 
        	List<T> updateList, List<T> remainderList) {
        	
        	// 遍历新列表
        	for(int i = 0; i < newList.size(); i++) {
        		T newItem = newList.get(i);
        		// 标记对象是否匹配
        		boolean found = false;
            	// 遍历旧列表,倒序
        		for (int j = oldList.size() - 1; j >= 0; j--) {
        		    T oldItem = oldList.get(j);
        		    // 比较两个对象
        		    int compare = compareTwoItem(fieldMap,newItem,oldItem);
        		    if (compare == 1) {
        			// 如果两个对象相同,加入相同列表中
        			sameList.add(newItem);
        			// 从列表中移除
        			oldList.remove(j);
        			found = true;
        			// 结束本轮遍历
        			break;
        		    }else if(compare == 2) {
        			// pass
        		    }else if(compare == 3) {
        			// 匹配对象,但属性不同,加入修改表中
        			updateList.add(newItem);
        			// 从列表中移除
        			oldList.remove(j);
        			found = true;
        			// 结束本轮遍历
        			break;
        		    }else {
        			// 发生异常
        			return;
        		    }
        		}
        		if (found == false) {
        		    // 如果本轮遍历,未找到匹配项,加入新增列表中
        		    addList.add(newItem);
        		}
        	}
        	
        	// oldList中剩余的项,加入剩余列表中
        	for(int i = 0; i < oldList.size(); i++) {
        		T oldItem = oldList.get(i);
        		remainderList.add(oldItem);
        	}
        	
        }
        
        /**
         * 
         * @methodName	: compareTwoItem
         * @description	: 比较两个相同类型的对象
         * @param <T>	: 泛型类型T
         * @param fieldMap	: 比较字段字典,key为字段名称,value为是否键值,1表示键值字段。
         * @param newItem	: 新的对象
         * @param oldItem	: 旧的对象
         * @return		: 返回值定义如下:
         * 		0	:	数据处理异常
         *  	1	:	对象完全相同(比较字段的字段值都相同)
         *  	2	:	对象不同(键值字段的字段值存在不相同)
         *  	3	:	对象相同,属性不同(键值字段的字段值相同,但属性值不同)
         * @history		:
         * ------------------------------------------------------------------------------
         * date		version		modifier		remarks                   
         * ------------------------------------------------------------------------------
         * 2021/08/15	1.0.0		sheng.zheng		初版
         *
         */
        @SuppressWarnings("unchecked")
    	public static <T> int compareTwoItem(Map<String,Integer> fieldMap,T newItem, T oldItem) {
        	int retCode = 1;
        	try {
                for (Map.Entry<String,Integer> entry : fieldMap.entrySet()) {
            	// 取得字段名称
            	String fieldName = entry.getKey();
            	Integer keyFlag = entry.getValue();
            		
            	// 取得新对象的当前字段值
        		Class<T> newClazz = (Class<T>) newItem.getClass();        		
            	Field newField = newClazz.getDeclaredField(fieldName);
            	newField.setAccessible(true);
            	Object newValue = newField.get(newItem);
            		
            	// 取得旧对象的当前字段值
        		Class<T> oldClazz = (Class<T>) oldItem.getClass();        		
            	Field oldField = oldClazz.getDeclaredField(fieldName);
            	oldField.setAccessible(true);
            	Object oldValue = oldField.get(oldItem);
            		
            	// 比较两个属性字段的值
            	if (!newValue.equals(oldValue)) {
            	    // 如果字段值不相等
            	    if (keyFlag == 1) {
            		// 如果为键值字段,表示两个对象
            		return 2;
            	    }else {
            		// 非键值字段,表示有属性发生改变
            		retCode = 3;
            	    }
            	}        		
                }
        	}catch (NoSuchFieldException e) {
        		e.printStackTrace();    
        		log.error(e.getMessage());
        		return 0;
        	}catch (IllegalAccessException e) {
        		e.printStackTrace();    		
        		log.error(e.getMessage());
        		return 0;
        	}
        	return retCode;
        }
        
        /**
         * 
         * @methodName	: removeDuplicate
         * @description	: 对象列表去重
         * @param <T>	: 泛型类型T
         * @param fieldMap	: 比较字段字典,key为字段名称,value为是否键值,1表示键值字段。
         * @param inputList	: 对象列表,将被去重
         * @param dupList	: 重复的多余对象列表,将被去重
         * @history		:
         * ------------------------------------------------------------------------------
         * date		version		modifier		remarks                   
         * ------------------------------------------------------------------------------
         * 2021/08/15	1.0.0		sheng.zheng		初版
         *
         */
        public static <T> void removeDuplicate(Map<String,Integer> fieldMap,List<T> inputList,List<T> dupList){
        	// 开始比较下标
        	int pos = 0;
        	while (true) {
        	    if (inputList.size() -1 < pos) {
        	        break;
        	    }    		
        	    // 标记对象是否重复
        	    boolean found = false;    		
        		
        	    // 被比较对象
        	    T compItem = inputList.get(pos);
        		
        	    // 遍历列表
        	    for (int i = pos + 1; i < inputList.size(); i++) {
        		// 比较对象
        		T newItem = inputList.get(i);
        		int compare = compareTwoItem(fieldMap,newItem,compItem);
        		if (compare == 1 || compare == 3) {
        			// 键值相同,为重复对象
        			found = true;
        			// 结束本次比较
        			break;
        		}
        	    }
        	    if (found == true) {
        		// 对象重复
        		dupList.add(compItem);
        		// 移除重复项
        		inputList.remove(pos);
        		// 注意,移除后,当前位置不变
        	    }else {
        		// 不重复,处理下一个
        		pos ++;    			
        	    }    		
        	}
        }
    }
    
    

    3、调用方法

      调用方法如下:

        	// 去重处理
        	// 字段字典
        	Map<String,Integer> fieldMap = new HashMap<String,Integer>();
    	// 手机号和用户名作为对象识别属性
        	fieldMap.put("phoneNumber", 1);
        	fieldMap.put("userName", 1);
        	List<UserInfo> dupList = new ArrayList<UserInfo>();
        	ObjectListUtil.removeDuplicate(fieldMap,importUserList,dupList);
    	// dupList可以作为导入错误数据输出用
    
        	// 查询数据库中存在的数据,orgId为导入附加参数,表示单位ID
        	List<UserInfo> dbUserList = userDao.selectItemsByOrgId(orgId);
    
        	// 对比新旧数据列表
        	List<UserInfo> addList = new ArrayList<UserInfo>();
        	List<UserInfo> updateList = new ArrayList<UserInfo>();
        	List<UserInfo> sameList = new ArrayList<UserInfo>();
        	List<UserInfo> remainderList = new ArrayList<UserInfo>();
        	
    	// 对象的其它属性字段,非键值
        	fieldMap.put("age", 0);
        	fieldMap.put("address", 0);    	    	
        	fieldMap.put("height", 0);
        	ObjectListUtil.compareTwoList(fieldMap, importUserList, dbUserList, 
        		addList, sameList, updateList, remainderList);
    
        	// 对addList执行insert操作,可以批量insert
    
        	// 对updateList执行update操作,逐个update
    
    
    作者:阿拉伯1999
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
    养成良好习惯,好文章随手顶一下。
  • 相关阅读:
    jQuery EasyUI实现全部关闭tabs
    设计与实现模块管理系统基本功能定义自己(28--所述多个模块之间的关联[4])
    C++11的一些功能
    表和视图之间的区别
    三个思路来实现自己定义404页面
    WebGL 在 OpenGL ES 指令 iOS 在 C 分歧版指令分析
    hdoj 2183 奇数阶魔方(II) 【模拟】+【法】
    新浪、万网前系统架构师高俊峰:统一监控报警平台架构设计思路
    this compilation unit is not on the build path of a java project
    Ecshop wap
  • 原文地址:https://www.cnblogs.com/alabo1999/p/15164417.html
Copyright © 2011-2022 走看看