# data-compare **Repository Path**: lanhuageng/data-compare ## Basic Information - **Project Name**: data-compare - **Description**: 专注于数据比较工具的开发,提供高效、准确的数据对比功能,适用于数据库同步、数据迁移及测试验证等多种场景。 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2025-02-12 - **Last Updated**: 2026-01-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: compare, 比较, 数据比较 ## README # data-compare #### 介绍 专注于数据比较工具的开发,提供高效、准确的数据对比功能,适用于数据核对、数据库同步、数据迁移及测试验证等多种场景。 我的优点: - java版本仅需jdk1.8即可 - maven项目,几乎无依赖(junit测试,mybatis为provide属性),适用性广 - 支持大数据核对,且占用内存少 - 对象比较时,还可以支持注解方式快速设定比较策略 - 比较结果清晰 > 关于核对效率
> 数据量少时,完全是内存中比较,非常快
> 数据量大时,受数据库性能影响。亲测左右各100W+数据核对时,时间大约为3分钟,不吃内存(本人笔记本上安装的oracle数据库,性能将就) #### 使用前准备 1. 如果我已经发布到网上仓库,那么直接在maven项目的pom.xml引入即可 2. 或是下载jar包引入使用 3. 最笨的方式,拷贝源码到项目中去,放心,核心代码不需要额外依赖第三方jar包 4. [20260130]当前项目热度较低,未发布到网上仓库,若你们的项目要使用,请采用第3点,直接拷贝源码的方式使用(注意,拷贝的源码最好不要改动包名路径)。若拷贝的代码在你们的项目中存在错误,请反馈给我,我这边会修改调整 #### 使用说明 支持多种方式进行数据比较,可以两个对象之间比较,也可以两个对象集合进行比较。项目中的有完全的测试类([cn.stalk.compare.test.Tests](src/test/java/cn/stalk/compare/test/Tests.java))用来演示使用方法。不说了,直接上代码 1. 方式一:一一比对,指定属性比较 ```java // 对象方式 User u1=new User().setAge(1).setName("AA"); User u2=new User().setAge(2).setName("BB"); ComparisonResult result1=DataComparator.compare(u1,u2,User::getAge,User::getName); System.out.println(result1.hasDifference()); // true // Map方式 Map map1=newHashMap("A",123,"B","bbb"); Map map2=newHashMap("A",123,"B","bbb"); ComparisonResult>result2=DataComparator.compare(map1,map2,e->e.get("A"),e->e.get("B")); System.out.println(result2.hasDifference()); // false ``` 2. 方式二:对象类集合比较,使用主键来标注主键属性、比较属性 ```java // 在类属性上使用注解@AComparable List users1=Arrays.asList( new User().setId("1").setName("张三").setTags("乒乓球,篮球").setAge(18) ,new User().setId("2").setName("李四").setTags("篮球").setAge(17) ,new User().setId("3").setName("李四2").setTags("篮球").setAge(17) ); List users2=Arrays.asList( new User().setId("1").setName("张三").setTags("乒乓球,篮球").setAge(18) ,new User().setId("1").setName("张三").setTags("乒乓球,篮球").setAge(18) ,new User().setId("2").setName("李四").setTags("篮球").setAge(18) ); FieldComparator userComparator=new FieldComparator<>(User.class); ComparisonResult userResult=userComparator.compare(users1,users2); System.out.println(userResult.hasDifference()); // true handleUserResult(userResult); // 在类上使用注解@AComparables List scores1=Arrays.asList( new Score().setUserId("1").setCourse("语文").setScore(50) ,new Score().setUserId("2").setCourse("语文").setScore(70) ); List scores2=Arrays.asList( new Score().setUserId("1").setCourse("语文").setScore(50) ,new Score().setUserId("2").setCourse("语文").setScore(70) ); FieldComparator scoreComparator=new FieldComparator<>(Score.class); ComparisonResult scoreResult=scoreComparator.compare(scores1,scores2); System.out.println(scoreResult.hasDifference()); // false ``` 3. 方式三:Map类集合比较,指定主键属性、比较属性 ```java List>map1=Arrays.asList( newHashMap("A",1,"B",1,"C","c","D","d","E",2) ,newHashMap("A",1,"B",2,"C","c","D","d","E",2) ); List>map2=Arrays.asList( newHashMap("A",1,"B",1,"C","c","D","d","E",2) ,newHashMap("A",1,"B",2,"C","c","D","d","E",2) ); MapComparator mapComparator=new MapComparator(); mapComparator.setKeyFields("A","B"); // 联合主键A+B mapComparator.setKeyFields("C","D","E"); // 比较属性 ComparisonResult>mapResult=mapComparator.compare(map1,map2); System.out.println(mapResult.hasDifference()); // false ``` 4. 方式四:从数据库流式读取数据进行比较。 > 注意1:左右两侧的数据必须是排好序的,且排序逻辑一致!!!
> 注意2:左右两侧数据需要在调用start方法前,异步push到比较器中!!!
> 注意3:处理比较结果是在主线程,但处理过程是分片的,并不是一次性处理的 ```java MapComparator mapComparator=new MapComparator(); mapComparator.setKeyFields("A","B"); mapComparator.setKeyFields("COL1","COL2","COL3"); // 可以使用StreamComparator,mybatis环境下也可以使用ResultStreamComparator StreamComparator>streamComparator=new StreamComparator<>(mapComparator,result->{ // 在这里处理比较结果,此处理方法会多次调用 System.out.println(result.hasDifference()); }); // 异步读取左侧数据 CompletableFuture.runAsync(()->{ // 模拟:流式查询数据库,将结果放入比较器中,数据读取完后,需要调用stop来停止 // 提示:在mybatis中,可以直接使用ResultStreamComparator.buildSourceHandler()作为流式处理Handler for(int i=0;i< 100;i++){ streamComparator.pushSource(newHashMap("A",1,"B",i/20,"COL1",null,"COL2","a","COL3",3)); } streamComparator.stopSource(); }); // 异步读取右侧数据,与左侧类似 CompletableFuture.runAsync(()->{ for(int i=0;i< 100;i++){ streamComparator.pushTarget(newHashMap("A",1,"B",i/20,"COL1",null,"COL2","a","COL3",3)); } streamComparator.stopTarget(); }); // 开始比较 streamComparator.start(); ``` #### 比较果处理 ```java private void handleUserResult(ComparisonResult result){ System.out.println("====================重复数据"); System.out.println("source中重复的主键"+result.getDuplicateSourceKeys()); System.out.println("target中重复的主键"+result.getDuplicateTargetKeys()); System.out.println("====================无效数据,即无法进行比较的数据,也指左右其中一侧出现重复主键的数据"); result.getInvalid().forEach(pair->{ System.out.println("无法比较的数据,主键是:"+pair.getKeys()); System.out.println("无法比较时,source中重复数据条数"+pair.getSource().size()); System.out.println("无法比较时,target中重复数据条数"+pair.getTarget().size()); }); System.out.println("====================source中多余数据"); result.getRemainingSources().forEach((k,list)->{ System.out.println("source主键"+k+",它对应的数据条数:"+list.size()); }); // 略:对应的还有target多余数据 System.out.println("====================不一样的数据"); result.getChanged().forEach(pair->{ pair.getSource(); // 不一样时,source原对象, pair.getTarget(); // .... System.out.println("不一样时主键:"+pair.getKeys()); // 不一样的数据 pair.getValues().forEach((k,diffValue)->{ System.out.println("主键="+k+",source="+diffValue.getSourceValue()+",target="+diffValue.getTargetValue()); }); }); System.out.println("===================相同的数据"); result.getSames().forEach(pair->{ System.out.println("相同时主键:"+pair.getKeys()); }); } ```