You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 10:07:40