import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
* 只读探针:按主键逐行比对两个库的同名表,回答「同一主键在两边是不是同一份内容」。
*
*
用法:java RowCompare <左库url> <右库url> [表名,表名...]
* 默认比对内置的核心业务表清单。不做任何写入。
*/
public class RowCompare {
private static final String[] DEFAULT_TABLES = {
"merchant", "merchant_store", "merchant_product", "merchant_operator",
"merchant_onboarding_application", "merchant_onboarding_progress", "merchant_bank_card",
"merchant_verification_attempt", "merchant_agreement_acceptance",
"benefit_order", "verify_record", "payment_transaction", "order_refund",
"payment_reconciliation_issue", "payment_callback_event",
"talent_user", "talent_card", "talent_dynamic_code",
"admin_user", "merchant_coupon", "news_article",
};
public static void main(String[] args) throws Exception {
try (Connection left = DriverManager.getConnection(args[0], "root", "123456");
Connection right = DriverManager.getConnection(args[1], "root", "123456")) {
String[] tables = args.length > 2 ? args[2].split(",") : DEFAULT_TABLES;
System.out.printf("%-34s %7s %7s %7s %7s %7s %7s%n",
"表", "总数L", "总数R", "同内容", "内容不同", "仅左", "仅右");
for (String table : tables) {
compare(left, right, table.trim());
}
}
}
private static void compare(Connection left, Connection right, String table) {
try {
List keys = primaryKey(left, table);
if (keys.isEmpty()) {
System.out.printf("%-34s %7s%n", table, "(无单列主键,跳过)");
return;
}
String key = keys.get(0);
// 只比对两边都有的列:本机库可能还没应用某些迁移,列集合不同不代表行内容不同。
List leftColumns = columns(left, table);
List rightColumns = columns(right, table);
List shared = new ArrayList<>(leftColumns);
shared.retainAll(rightColumns);
if (!shared.contains(key)) {
System.out.printf("%-34s %7s%n", table, "(主键列不在共有列里)");
return;
}
Map> leftRows = fingerprint(left, table, key, shared);
Map> rightRows = fingerprint(right, table, key, shared);
int same = 0;
int different = 0;
List conflicts = new ArrayList<>();
List example = null;
for (Map.Entry> entry : leftRows.entrySet()) {
List other = rightRows.get(entry.getKey());
if (other == null) continue;
if (other.equals(entry.getValue())) same++;
else {
different++;
if (conflicts.size() < 8) conflicts.add(entry.getKey());
if (example == null) example = differingColumns(shared, entry.getValue(), other);
}
}
int onlyLeft = 0;
for (String id : leftRows.keySet()) if (!rightRows.containsKey(id)) onlyLeft++;
int onlyRight = 0;
for (String id : rightRows.keySet()) if (!leftRows.containsKey(id)) onlyRight++;
System.out.printf("%-34s %7d %7d %7d %7d %7d %7d%n",
table, leftRows.size(), rightRows.size(), same, different, onlyLeft, onlyRight);
if (!conflicts.isEmpty()) {
System.out.println(" 主键相同但内容不同:" + String.join(", ", conflicts)
+ (different > conflicts.size() ? " ...(共 " + different + " 条)" : ""));
if (example != null) System.out.println(" 差异列(示例):" + String.join(", ", example));
}
} catch (Exception exception) {
System.out.printf("%-34s %7s%n", table, "(" + exception.getMessage() + ")");
}
}
private static List differingColumns(List columns, List left, List right) {
List differing = new ArrayList<>();
for (int index = 0; index < columns.size(); index++) {
if (!left.get(index).equals(right.get(index))) differing.add(columns.get(index));
}
return differing;
}
private static List columns(Connection connection, String table) throws Exception {
List columns = new ArrayList<>();
try (PreparedStatement statement = connection.prepareStatement("""
select column_name from information_schema.columns
where table_schema = database() and table_name = ? order by ordinal_position
""")) {
statement.setString(1, table);
try (ResultSet rows = statement.executeQuery()) {
while (rows.next()) columns.add(rows.getString(1));
}
}
return columns;
}
private static List primaryKey(Connection connection, String table) throws Exception {
List keys = new ArrayList<>();
try (PreparedStatement statement = connection.prepareStatement("""
select column_name from information_schema.key_column_usage
where table_schema = database() and table_name = ? and constraint_name = 'PRIMARY'
order by ordinal_position
""")) {
statement.setString(1, table);
try (ResultSet rows = statement.executeQuery()) {
while (rows.next()) keys.add(rows.getString(1));
}
}
return keys;
}
/** 主键 -> 共有列的内容指纹(按列顺序拼串,null 用特殊标记)。 */
private static Map> fingerprint(Connection connection, String table, String key,
List columns) throws Exception {
Map> rows = new TreeMap<>();
String selection = String.join(", ", columns.stream().map(column -> "`" + column + "`").toList());
try (PreparedStatement statement = connection.prepareStatement(
"select " + selection + " from `" + table + "`");
ResultSet result = statement.executeQuery()) {
while (result.next()) {
List content = new ArrayList<>(columns.size());
for (int index = 1; index <= columns.size(); index++) {
Object value = result.getObject(index);
content.add(value == null ? "\u0000" : String.valueOf(value));
}
rows.put(String.valueOf(result.getObject(key)), content);
}
}
return rows;
}
}