如何用Java对比两同结构数据库表数据?以PRODUCT_TYPE表为例
嘿,刚好我之前处理过几乎一模一样的数据库变更识别需求,给你整理了一个Java实现方案,完全适配你说的DEVDB和UATDB中RAPP.PRODUCT_TYPE表的对比场景——识别DEV里的新增行,以及已有行的TYPE/CLASS字段修改情况。
核心思路
因为列结构是固定的,咱们就用PROD_TYPE_ID作为唯一主键来匹配两边的数据:
- 遍历DEV的所有数据,找出UAT里没有的
PROD_TYPE_ID,这些就是新增行 - 对于两边都存在的
PROD_TYPE_ID,直接对比TYPE和CLASS字段的值,只要有一个不一样,就是修改行
实现步骤
- 第一步:配置两个数据库的连接信息,确保能分别连接到DEVDB和UATDB
- 第二步:从两个库中查询
RAPP.PRODUCT_TYPE的全量数据,把数据封装成以PROD_TYPE_ID为Key的Map,这样后续对比效率更高 - 第三步:编写对比逻辑,分别筛选新增和修改的记录
- 第四步:输出或者持久化对比结果(比如打印到控制台、写入日志或者生成报告)
完整代码示例
首先定义实体类,用来存储表的行数据:
import java.util.Objects; public class ProductType { private Integer prodTypeId; private String type; private String clazz; // 用clazz避免和Java关键字class冲突 // 构造方法 public ProductType(Integer prodTypeId, String type, String clazz) { this.prodTypeId = prodTypeId; this.type = type; this.clazz = clazz; } // 省略getter和setter方法,自行补充 public Integer getProdTypeId() { return prodTypeId; } public void setProdTypeId(Integer prodTypeId) { this.prodTypeId = prodTypeId; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getClazz() { return clazz; } public void setClazz(String clazz) { this.clazz = clazz; } // 重写equals,只对比TYPE和CLASS字段 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ProductType that = (ProductType) o; return Objects.equals(type, that.type) && Objects.equals(clazz, that.clazz); } @Override public int hashCode() { return Objects.hash(type, clazz); } }
然后是数据库操作和对比的核心代码:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.HashMap; import java.util.Map; public class DbChangeDetector { // DEVDB连接配置,根据实际环境修改 private static final String DEV_URL = "jdbc:mysql://dev-host:3306/DEVDB?useSSL=false&serverTimezone=UTC"; private static final String DEV_USER = "dev_user"; private static final String DEV_PWD = "dev_pwd"; // UATDB连接配置,根据实际环境修改 private static final String UAT_URL = "jdbc:mysql://uat-host:3306/UATDB?useSSL=false&serverTimezone=UTC"; private static final String UAT_USER = "uat_user"; private static final String UAT_PWD = "uat_pwd"; private static final String QUERY_SQL = "SELECT PROD_TYPE_ID, TYPE, CLASS FROM RAPP.PRODUCT_TYPE"; public static void main(String[] args) { try { // 获取两个库的数据Map Map<Integer, ProductType> devData = fetchProductTypeData(DEV_URL, DEV_USER, DEV_PWD); Map<Integer, ProductType> uatData = fetchProductTypeData(UAT_URL, UAT_USER, UAT_PWD); // 执行对比逻辑 detectChanges(devData, uatData); } catch (Exception e) { e.printStackTrace(); } } // 从指定数据库查询PRODUCT_TYPE数据并封装为Map private static Map<Integer, ProductType> fetchProductTypeData(String url, String user, String pwd) throws Exception { Map<Integer, ProductType> dataMap = new HashMap<>(); // 用try-with-resources自动关闭资源,避免泄漏 try (Connection conn = DriverManager.getConnection(url, user, pwd); PreparedStatement stmt = conn.prepareStatement(QUERY_SQL); ResultSet rs = stmt.executeQuery()) { while (rs.next()) { Integer prodTypeId = rs.getInt("PROD_TYPE_ID"); String type = rs.getString("TYPE"); String clazz = rs.getString("CLASS"); dataMap.put(prodTypeId, new ProductType(prodTypeId, type, clazz)); } } return dataMap; } // 对比两个数据集,识别新增和修改记录 private static void detectChanges(Map<Integer, ProductType> devData, Map<Integer, ProductType> uatData) { // 识别新增行:DEV存在、UAT不存在的记录 System.out.println("=== 新增行记录 ==="); for (Map.Entry<Integer, ProductType> entry : devData.entrySet()) { Integer prodTypeId = entry.getKey(); if (!uatData.containsKey(prodTypeId)) { ProductType devRow = entry.getValue(); System.out.printf("PROD_TYPE_ID: %d, TYPE: %s, CLASS: %s%n", devRow.getProdTypeId(), devRow.getType(), devRow.getClazz()); } } // 识别修改行:两边都存在,但TYPE/CLASS不一致的记录 System.out.println("\n=== 修改行记录 ==="); for (Map.Entry<Integer, ProductType> entry : devData.entrySet()) { Integer prodTypeId = entry.getKey(); if (uatData.containsKey(prodTypeId)) { ProductType devRow = entry.getValue(); ProductType uatRow = uatData.get(prodTypeId); if (!devRow.equals(uatRow)) { System.out.printf("PROD_TYPE_ID: %d%n DEV -> TYPE: %s, CLASS: %s%n UAT -> TYPE: %s, CLASS: %s%n", prodTypeId, devRow.getType(), devRow.getClazz(), uatRow.getType(), uatRow.getClazz()); } } } } }
关键注意事项
- 资源管理:代码用了try-with-resources语法,确保数据库连接、Statement、ResultSet都会自动关闭,避免资源泄漏
- 性能优化:用Map存储数据,对比时是O(n)的时间复杂度,比双重循环高效;如果表数据量极大,建议改成分页查询,避免一次性加载太多数据导致内存溢出
- 字段对比:实体类重写了
equals和hashCode方法,只对比TYPE和CLASS,完全匹配你的需求(列结构不变,无需对比主键或其他字段) - 数据库兼容性:示例用的是MySQL的JDBC URL,如果你用Oracle、SQL Server等其他数据库,需要修改JDBC驱动和连接URL格式
- 日志替代:可以把控制台输出换成日志框架(比如SLF4J+Logback),方便后续排查问题
内容的提问来源于stack exchange,提问作者Arun PM
相关产品推荐
相关产品推荐

