531 lines
20 KiB
Java
531 lines
20 KiB
Java
|
|
package org.jeecg;
|
|||
|
|
|
|||
|
|
import java.io.FileInputStream;
|
|||
|
|
import java.io.FileOutputStream;
|
|||
|
|
import java.sql.*;
|
|||
|
|
import java.text.SimpleDateFormat;
|
|||
|
|
import java.util.*;
|
|||
|
|
import java.util.Date;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Oracle数据库同步工具类
|
|||
|
|
* 功能:根据指定的字段类型(日期或ID)分批同步数据,并记录同步位置
|
|||
|
|
*/
|
|||
|
|
public class OracleSync {
|
|||
|
|
// 数据库连接信息
|
|||
|
|
private static final String SOURCE_URL = "jdbc:oracle:thin:@//127.0.0.1:1521/orcl";
|
|||
|
|
private static final String TARGET_URL = "jdbc:oracle:thin:@//127.0.0.1:1521/orcl";
|
|||
|
|
private static final String USERNAME = "idctest";
|
|||
|
|
private static final String PASSWORD = "12345678";
|
|||
|
|
private static final String TARGET_USER = "REB";
|
|||
|
|
private static final String TARGET_PASSWORD = "REB";
|
|||
|
|
|
|||
|
|
// 分批同步配置
|
|||
|
|
private static final int BATCH_SIZE_ID = 10000; // ID每次同步数量
|
|||
|
|
private static final int BATCH_SIZE_DAYS = 30; // 日期每次同步天数
|
|||
|
|
|
|||
|
|
// 同步状态记录文件路径
|
|||
|
|
private static final String SYNC_STATUS_FILE = "sync_status.properties";
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 表配置类,用于存储表名、依据字段名和字段类型
|
|||
|
|
*/
|
|||
|
|
static class TableConfig {
|
|||
|
|
String tableName; // 表名
|
|||
|
|
String fieldName; // 依据字段名
|
|||
|
|
FieldType fieldType; // 字段类型(日期或ID)
|
|||
|
|
|
|||
|
|
public TableConfig(String tableName, String fieldName, FieldType fieldType) {
|
|||
|
|
this.tableName = tableName;
|
|||
|
|
this.fieldName = fieldName;
|
|||
|
|
this.fieldType = fieldType;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 字段类型枚举
|
|||
|
|
*/
|
|||
|
|
enum FieldType {
|
|||
|
|
DATE, // 日期类型字段
|
|||
|
|
ID // ID类型字段
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 日期范围类,用于存储最小和最大日期
|
|||
|
|
*/
|
|||
|
|
static class DateRange {
|
|||
|
|
Date minDate; // 最小日期
|
|||
|
|
Date maxDate; // 最大日期
|
|||
|
|
|
|||
|
|
public DateRange(Date minDate, Date maxDate) {
|
|||
|
|
this.minDate = minDate;
|
|||
|
|
this.maxDate = maxDate;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* ID范围类,用于存储最小和最大ID
|
|||
|
|
*/
|
|||
|
|
static class IdRange {
|
|||
|
|
long minId; // 最小ID
|
|||
|
|
long maxId; // 最大ID
|
|||
|
|
|
|||
|
|
public IdRange(long minId, long maxId) {
|
|||
|
|
this.minId = minId;
|
|||
|
|
this.maxId = maxId;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 主方法,程序入口
|
|||
|
|
* @param args 命令行参数
|
|||
|
|
*/
|
|||
|
|
public static void main(String[] args) {
|
|||
|
|
// 指定要同步的表名和依据字段
|
|||
|
|
List<TableConfig> tableConfigs = Arrays.asList(
|
|||
|
|
new TableConfig("TABLE1", "CREATE_DATE", FieldType.DATE),
|
|||
|
|
new TableConfig("TABLE2", "ID", FieldType.ID)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 加载上次同步状态
|
|||
|
|
Properties syncStatus = loadSyncStatus();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 1. 验证指定的表是否存在
|
|||
|
|
List<TableConfig> validTables = validateTables(tableConfigs);
|
|||
|
|
|
|||
|
|
if (validTables.isEmpty()) {
|
|||
|
|
System.out.println("没有有效的表需要同步");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 在目标库创建表结构
|
|||
|
|
createTargetTables(validTables);
|
|||
|
|
|
|||
|
|
// 3. 同步数据(根据字段类型分批)
|
|||
|
|
syncDataByFieldType(validTables, syncStatus);
|
|||
|
|
|
|||
|
|
System.out.println("所有指定表同步完成");
|
|||
|
|
} catch (SQLException e) {
|
|||
|
|
System.err.println("数据库操作错误: " + e.getMessage());
|
|||
|
|
e.printStackTrace();
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
System.err.println("系统错误: " + e.getMessage());
|
|||
|
|
e.printStackTrace();
|
|||
|
|
} finally {
|
|||
|
|
// 保存同步状态
|
|||
|
|
saveSyncStatus(syncStatus);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 验证表是否存在且包含指定字段
|
|||
|
|
* @param tableConfigs 表配置列表
|
|||
|
|
* @return 有效的表配置列表
|
|||
|
|
* @throws SQLException 数据库异常
|
|||
|
|
*/
|
|||
|
|
private static List<TableConfig> validateTables(List<TableConfig> tableConfigs) throws SQLException {
|
|||
|
|
List<TableConfig> validTables = new ArrayList<>();
|
|||
|
|
try (Connection sourceConn = DriverManager.getConnection(SOURCE_URL, USERNAME, PASSWORD)) {
|
|||
|
|
for (TableConfig config : tableConfigs) {
|
|||
|
|
if (isTableExists(sourceConn, USERNAME, config.tableName)) {
|
|||
|
|
if (isColumnExists(sourceConn, USERNAME, config.tableName, config.fieldName)) {
|
|||
|
|
validTables.add(config);
|
|||
|
|
System.out.println("表 " + config.tableName + " 存在,将根据字段 " + config.fieldName + " 分批同步");
|
|||
|
|
} else {
|
|||
|
|
System.out.println("警告: 表 " + config.tableName + " 中不存在字段 " + config.fieldName + ",已跳过");
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
System.out.println("警告: 表 " + config.tableName + " 在源数据库中不存在,已跳过");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return validTables;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 在目标库创建表结构
|
|||
|
|
* @param tableConfigs 表配置列表
|
|||
|
|
* @throws SQLException 数据库异常
|
|||
|
|
*/
|
|||
|
|
private static void createTargetTables(List<TableConfig> tableConfigs) throws SQLException {
|
|||
|
|
try (Connection sourceConn = DriverManager.getConnection(SOURCE_URL, USERNAME, PASSWORD);
|
|||
|
|
Connection targetConn = DriverManager.getConnection(TARGET_URL, TARGET_USER, TARGET_PASSWORD)) {
|
|||
|
|
|
|||
|
|
for (TableConfig config : tableConfigs) {
|
|||
|
|
try {
|
|||
|
|
// 检查表是否已存在(区分大小写)
|
|||
|
|
if (!isTableExists(targetConn, TARGET_USER, config.tableName)) {
|
|||
|
|
// 获取源表结构
|
|||
|
|
String createSql = getCreateTableSql(sourceConn, config.tableName);
|
|||
|
|
|
|||
|
|
// 在目标库创建表
|
|||
|
|
try (Statement stmt = targetConn.createStatement()) {
|
|||
|
|
stmt.execute(createSql);
|
|||
|
|
System.out.println("表 " + config.tableName + " 创建成功");
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
e.printStackTrace();
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
System.out.println("表 " + config.tableName + " 已存在,跳过创建");
|
|||
|
|
}
|
|||
|
|
} catch (SQLException e) {
|
|||
|
|
System.err.println("处理表 " + config.tableName + " 时出错: " + e.getMessage());
|
|||
|
|
throw e;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据字段类型同步数据
|
|||
|
|
* @param tableConfigs 表配置列表
|
|||
|
|
* @param syncStatus 同步状态
|
|||
|
|
* @throws SQLException 数据库异常
|
|||
|
|
*/
|
|||
|
|
private static void syncDataByFieldType(List<TableConfig> tableConfigs, Properties syncStatus) throws SQLException {
|
|||
|
|
try (Connection sourceConn = DriverManager.getConnection(SOURCE_URL, USERNAME, PASSWORD);
|
|||
|
|
Connection targetConn = DriverManager.getConnection(TARGET_URL, TARGET_USER, TARGET_PASSWORD)) {
|
|||
|
|
|
|||
|
|
// 设置目标连接为批量提交模式
|
|||
|
|
targetConn.setAutoCommit(false);
|
|||
|
|
|
|||
|
|
for (TableConfig config : tableConfigs) {
|
|||
|
|
System.out.println("开始同步表: " + config.tableName + " (依据字段: " + config.fieldName + ")");
|
|||
|
|
long startTime = System.currentTimeMillis();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
if (config.fieldType == FieldType.DATE) {
|
|||
|
|
syncByDateRange(sourceConn, targetConn, config, syncStatus);
|
|||
|
|
} else if (config.fieldType == FieldType.ID) {
|
|||
|
|
syncByIdRange(sourceConn, targetConn, config, syncStatus);
|
|||
|
|
}
|
|||
|
|
} catch (SQLException e) {
|
|||
|
|
System.err.println("同步表 " + config.tableName + " 时出错: " + e.getMessage());
|
|||
|
|
targetConn.rollback();
|
|||
|
|
throw e;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
long endTime = System.currentTimeMillis();
|
|||
|
|
System.out.println("表 " + config.tableName + " 同步耗时: " + (endTime - startTime) + " 毫秒");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 按日期范围同步数据
|
|||
|
|
* @param sourceConn 源数据库连接
|
|||
|
|
* @param targetConn 目标数据库连接
|
|||
|
|
* @param config 表配置
|
|||
|
|
* @param syncStatus 同步状态
|
|||
|
|
* @throws SQLException 数据库异常
|
|||
|
|
*/
|
|||
|
|
private static void syncByDateRange(Connection sourceConn, Connection targetConn,
|
|||
|
|
TableConfig config, Properties syncStatus) throws SQLException {
|
|||
|
|
// 获取最小和最大日期
|
|||
|
|
DateRange dateRange = getDateRange(sourceConn, config);
|
|||
|
|
System.out.println("日期范围: " + dateRange.minDate + " 至 " + dateRange.maxDate);
|
|||
|
|
|
|||
|
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|||
|
|
|
|||
|
|
// 获取上次同步的位置
|
|||
|
|
Date lastSyncedDate = getLastSyncedDate(config.tableName, syncStatus);
|
|||
|
|
Date currentStart = (lastSyncedDate != null && lastSyncedDate.after(dateRange.minDate)) ?
|
|||
|
|
lastSyncedDate : dateRange.minDate;
|
|||
|
|
|
|||
|
|
while (currentStart.before(dateRange.maxDate)) {
|
|||
|
|
Date currentEnd = addDays(currentStart, BATCH_SIZE_DAYS);
|
|||
|
|
if (currentEnd.after(dateRange.maxDate)) {
|
|||
|
|
currentEnd = dateRange.maxDate;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
String whereClause = config.fieldName + " BETWEEN TO_DATE('" + sdf.format(currentStart) +
|
|||
|
|
"', 'YYYY-MM-DD') AND TO_DATE('" + sdf.format(currentEnd) + "', 'YYYY-MM-DD')";
|
|||
|
|
|
|||
|
|
System.out.println("同步日期范围: " + sdf.format(currentStart) + " 至 " + sdf.format(currentEnd));
|
|||
|
|
|
|||
|
|
int rowsSynced = syncDataBatch(sourceConn, targetConn, config.tableName, whereClause);
|
|||
|
|
System.out.println("已同步 " + rowsSynced + " 行数据");
|
|||
|
|
|
|||
|
|
// 记录同步位置
|
|||
|
|
syncStatus.setProperty(config.tableName + ".lastSyncedDate", sdf.format(currentEnd));
|
|||
|
|
saveSyncStatus(syncStatus); // 实时保存状态
|
|||
|
|
|
|||
|
|
currentStart = currentEnd;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 按ID范围同步数据
|
|||
|
|
* @param sourceConn 源数据库连接
|
|||
|
|
* @param targetConn 目标数据库连接
|
|||
|
|
* @param config 表配置
|
|||
|
|
* @param syncStatus 同步状态
|
|||
|
|
* @throws SQLException 数据库异常
|
|||
|
|
*/
|
|||
|
|
private static void syncByIdRange(Connection sourceConn, Connection targetConn,
|
|||
|
|
TableConfig config, Properties syncStatus) throws SQLException {
|
|||
|
|
// 获取最小和最大ID
|
|||
|
|
IdRange idRange = getIdRange(sourceConn, config);
|
|||
|
|
System.out.println("ID范围: " + idRange.minId + " 至 " + idRange.maxId);
|
|||
|
|
|
|||
|
|
// 获取上次同步的位置
|
|||
|
|
long lastSyncedId = getLastSyncedId(config.tableName, syncStatus);
|
|||
|
|
long currentStart = (lastSyncedId > 0 && lastSyncedId > idRange.minId) ?
|
|||
|
|
lastSyncedId : idRange.minId;
|
|||
|
|
|
|||
|
|
while (currentStart <= idRange.maxId) {
|
|||
|
|
long currentEnd = currentStart + BATCH_SIZE_ID - 1;
|
|||
|
|
if (currentEnd > idRange.maxId) {
|
|||
|
|
currentEnd = idRange.maxId;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
String whereClause = config.fieldName + " BETWEEN " + currentStart + " AND " + currentEnd;
|
|||
|
|
|
|||
|
|
System.out.println("同步ID范围: " + currentStart + " 至 " + currentEnd);
|
|||
|
|
|
|||
|
|
int rowsSynced = syncDataBatch(sourceConn, targetConn, config.tableName, whereClause);
|
|||
|
|
System.out.println("已同步 " + rowsSynced + " 行数据");
|
|||
|
|
|
|||
|
|
// 记录同步位置
|
|||
|
|
syncStatus.setProperty(config.tableName + ".lastSyncedId", String.valueOf(currentEnd));
|
|||
|
|
saveSyncStatus(syncStatus); // 实时保存状态
|
|||
|
|
|
|||
|
|
currentStart = currentEnd + 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取表的日期范围
|
|||
|
|
* @param conn 数据库连接
|
|||
|
|
* @param config 表配置
|
|||
|
|
* @return 日期范围对象
|
|||
|
|
* @throws SQLException 数据库异常
|
|||
|
|
*/
|
|||
|
|
private static DateRange getDateRange(Connection conn, TableConfig config) throws SQLException {
|
|||
|
|
String sql = "SELECT MIN(" + config.fieldName + ") as min_date, MAX(" + config.fieldName + ") as max_date FROM " +
|
|||
|
|
USERNAME + "." + config.tableName;
|
|||
|
|
|
|||
|
|
try (Statement stmt = conn.createStatement();
|
|||
|
|
ResultSet rs = stmt.executeQuery(sql)) {
|
|||
|
|
if (rs.next()) {
|
|||
|
|
return new DateRange(rs.getDate("min_date"), rs.getDate("max_date"));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
throw new SQLException("无法获取日期范围");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取表的ID范围
|
|||
|
|
* @param conn 数据库连接
|
|||
|
|
* @param config 表配置
|
|||
|
|
* @return ID范围对象
|
|||
|
|
* @throws SQLException 数据库异常
|
|||
|
|
*/
|
|||
|
|
private static IdRange getIdRange(Connection conn, TableConfig config) throws SQLException {
|
|||
|
|
String sql = "SELECT MIN(" + config.fieldName + ") as min_id, MAX(" + config.fieldName + ") as max_id FROM " +
|
|||
|
|
USERNAME + "." + config.tableName;
|
|||
|
|
|
|||
|
|
try (Statement stmt = conn.createStatement();
|
|||
|
|
ResultSet rs = stmt.executeQuery(sql)) {
|
|||
|
|
if (rs.next()) {
|
|||
|
|
return new IdRange(rs.getLong("min_id"), rs.getLong("max_id"));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
throw new SQLException("无法获取ID范围");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 同步一批数据
|
|||
|
|
* @param sourceConn 源数据库连接
|
|||
|
|
* @param targetConn 目标数据库连接
|
|||
|
|
* @param tableName 表名
|
|||
|
|
* @param whereClause 条件子句
|
|||
|
|
* @return 同步的行数
|
|||
|
|
* @throws SQLException 数据库异常
|
|||
|
|
*/
|
|||
|
|
private static int syncDataBatch(Connection sourceConn, Connection targetConn,
|
|||
|
|
String tableName, String whereClause) throws SQLException {
|
|||
|
|
int totalRows = 0;
|
|||
|
|
|
|||
|
|
// 从源表读取数据(使用带引号的表名保持大小写)
|
|||
|
|
String selectSql = "SELECT * FROM \"" + USERNAME.toUpperCase() + "\".\"" + tableName +
|
|||
|
|
"\" WHERE " + whereClause;
|
|||
|
|
|
|||
|
|
try (Statement sourceStmt = sourceConn.createStatement(
|
|||
|
|
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
|
|||
|
|
ResultSet rs = sourceStmt.executeQuery(selectSql)) {
|
|||
|
|
|
|||
|
|
// 设置获取大小为1000,优化大表读取
|
|||
|
|
sourceStmt.setFetchSize(1000);
|
|||
|
|
|
|||
|
|
// 获取列信息
|
|||
|
|
ResultSetMetaData metaData = rs.getMetaData();
|
|||
|
|
int columnCount = metaData.getColumnCount();
|
|||
|
|
|
|||
|
|
// 准备插入语句(使用带引号的表名保持大小写)
|
|||
|
|
StringBuilder insertSql = new StringBuilder("INSERT INTO \"")
|
|||
|
|
.append(TARGET_USER.toUpperCase()).append("\".\"").append(tableName).append("\" VALUES (");
|
|||
|
|
for (int i = 1; i <= columnCount; i++) {
|
|||
|
|
if (i > 1) insertSql.append(", ");
|
|||
|
|
insertSql.append("?");
|
|||
|
|
}
|
|||
|
|
insertSql.append(")");
|
|||
|
|
|
|||
|
|
// 批量插入
|
|||
|
|
try (PreparedStatement pstmt = targetConn.prepareStatement(insertSql.toString())) {
|
|||
|
|
int batchSize = 0;
|
|||
|
|
|
|||
|
|
while (rs.next()) {
|
|||
|
|
for (int i = 1; i <= columnCount; i++) {
|
|||
|
|
pstmt.setObject(i, rs.getObject(i));
|
|||
|
|
}
|
|||
|
|
pstmt.addBatch();
|
|||
|
|
batchSize++;
|
|||
|
|
totalRows++;
|
|||
|
|
|
|||
|
|
if (batchSize % 1000 == 0) {
|
|||
|
|
pstmt.executeBatch();
|
|||
|
|
targetConn.commit();
|
|||
|
|
batchSize = 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (batchSize > 0) {
|
|||
|
|
pstmt.executeBatch();
|
|||
|
|
targetConn.commit();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return totalRows;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查表是否存在
|
|||
|
|
* @param conn 数据库连接
|
|||
|
|
* @param schema 模式名
|
|||
|
|
* @param tableName 表名
|
|||
|
|
* @return 表是否存在
|
|||
|
|
* @throws SQLException 数据库异常
|
|||
|
|
*/
|
|||
|
|
private static boolean isTableExists(Connection conn, String schema, String tableName) throws SQLException {
|
|||
|
|
String sql = "SELECT COUNT(*) FROM all_tables WHERE owner = ? AND (table_name = ? OR table_name = ?)";
|
|||
|
|
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
|
|||
|
|
pstmt.setString(1, schema.toUpperCase());
|
|||
|
|
pstmt.setString(2, tableName.toUpperCase());
|
|||
|
|
pstmt.setString(3, tableName);
|
|||
|
|
try (ResultSet rs = pstmt.executeQuery()) {
|
|||
|
|
return rs.next() && rs.getInt(1) > 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查列是否存在
|
|||
|
|
* @param conn 数据库连接
|
|||
|
|
* @param schema 模式名
|
|||
|
|
* @param tableName 表名
|
|||
|
|
* @param columnName 列名
|
|||
|
|
* @return 列是否存在
|
|||
|
|
* @throws SQLException 数据库异常
|
|||
|
|
*/
|
|||
|
|
private static boolean isColumnExists(Connection conn, String schema, String tableName, String columnName) throws SQLException {
|
|||
|
|
String sql = "SELECT COUNT(*) FROM all_tab_columns WHERE owner = ? AND table_name = ? AND column_name = ?";
|
|||
|
|
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
|
|||
|
|
pstmt.setString(1, schema.toUpperCase());
|
|||
|
|
pstmt.setString(2, tableName.toUpperCase());
|
|||
|
|
pstmt.setString(3, columnName.toUpperCase());
|
|||
|
|
try (ResultSet rs = pstmt.executeQuery()) {
|
|||
|
|
return rs.next() && rs.getInt(1) > 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取创建表的SQL语句
|
|||
|
|
* @param conn 数据库连接
|
|||
|
|
* @param table 表名
|
|||
|
|
* @return 创建表的SQL语句
|
|||
|
|
* @throws SQLException 数据库异常
|
|||
|
|
*/
|
|||
|
|
private static String getCreateTableSql(Connection conn, String table) throws SQLException {
|
|||
|
|
if (!isTableExists(conn, USERNAME, table)) {
|
|||
|
|
throw new SQLException("表 " + table + " 在源数据库中不存在");
|
|||
|
|
}
|
|||
|
|
return "CREATE TABLE \"" + table + "\" AS SELECT * FROM \"" +
|
|||
|
|
USERNAME.toUpperCase() + "\".\"" + table + "\" WHERE 1=0";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 加载同步状态
|
|||
|
|
* @return 同步状态Properties对象
|
|||
|
|
*/
|
|||
|
|
private static Properties loadSyncStatus() {
|
|||
|
|
Properties props = new Properties();
|
|||
|
|
try {
|
|||
|
|
props.load(new FileInputStream(SYNC_STATUS_FILE));
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
// 文件不存在或其他错误,返回空Properties
|
|||
|
|
}
|
|||
|
|
return props;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 保存同步状态
|
|||
|
|
* @param syncStatus 同步状态Properties对象
|
|||
|
|
*/
|
|||
|
|
private static void saveSyncStatus(Properties syncStatus) {
|
|||
|
|
try {
|
|||
|
|
syncStatus.store(new FileOutputStream(SYNC_STATUS_FILE),
|
|||
|
|
"Oracle Sync Status - " + new Date());
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
System.err.println("无法保存同步状态: " + e.getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取上次同步的日期
|
|||
|
|
* @param tableName 表名
|
|||
|
|
* @param syncStatus 同步状态
|
|||
|
|
* @return 上次同步的日期,如果没有则返回null
|
|||
|
|
*/
|
|||
|
|
private static Date getLastSyncedDate(String tableName, Properties syncStatus) {
|
|||
|
|
try {
|
|||
|
|
String dateStr = syncStatus.getProperty(tableName + ".lastSyncedDate");
|
|||
|
|
if (dateStr != null) {
|
|||
|
|
return new SimpleDateFormat("yyyy-MM-dd").parse(dateStr);
|
|||
|
|
}
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
System.err.println("解析上次同步日期失败: " + e.getMessage());
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取上次同步的ID
|
|||
|
|
* @param tableName 表名
|
|||
|
|
* @param syncStatus 同步状态
|
|||
|
|
* @return 上次同步的ID,如果没有则返回0
|
|||
|
|
*/
|
|||
|
|
private static long getLastSyncedId(String tableName, Properties syncStatus) {
|
|||
|
|
try {
|
|||
|
|
String idStr = syncStatus.getProperty(tableName + ".lastSyncedId");
|
|||
|
|
if (idStr != null) {
|
|||
|
|
return Long.parseLong(idStr);
|
|||
|
|
}
|
|||
|
|
} catch (Exception e) {
|
|||
|
|
System.err.println("解析上次同步ID失败: " + e.getMessage());
|
|||
|
|
}
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 计算指定日期加上指定天数后的日期
|
|||
|
|
* @param date 基准日期
|
|||
|
|
* @param days 要添加的天数
|
|||
|
|
* @return 计算后的日期
|
|||
|
|
*/
|
|||
|
|
private static Date addDays(Date date, int days) {
|
|||
|
|
long time = date.getTime() + (long)days * 24 * 60 * 60 * 1000;
|
|||
|
|
return new Date(time);
|
|||
|
|
}
|
|||
|
|
}
|