|
|
|
|
@ -15,8 +15,14 @@ import java.util.logging.Logger;
|
|
|
|
|
public class DatabaseMigrationTool {
|
|
|
|
|
private static final Logger LOGGER = Logger.getLogger(DatabaseMigrationTool.class.getName());
|
|
|
|
|
|
|
|
|
|
// 表名常量
|
|
|
|
|
private static final String TABLE_BOOKS = "books";
|
|
|
|
|
private static final String TABLE_USERS = "users";
|
|
|
|
|
private static final String TABLE_LOANS = "loans";
|
|
|
|
|
private static final String COLUMN_COUNT = "count";
|
|
|
|
|
|
|
|
|
|
// 允许操作的表白名单(防止SQL注入)
|
|
|
|
|
private static final Set<String> ALLOWED_TABLES = Set.of("books", "users", "loans");
|
|
|
|
|
private static final Set<String> ALLOWED_TABLES = Set.of(TABLE_BOOKS, TABLE_USERS, TABLE_LOANS);
|
|
|
|
|
|
|
|
|
|
private final DatabaseAdapter sourceAdapter;
|
|
|
|
|
private final DatabaseAdapter targetAdapter;
|
|
|
|
|
@ -45,15 +51,15 @@ public class DatabaseMigrationTool {
|
|
|
|
|
try {
|
|
|
|
|
// 1. 迁移图书数据
|
|
|
|
|
int booksCount = migrateBooks();
|
|
|
|
|
LOGGER.info("已迁移" + booksCount + "本图书");
|
|
|
|
|
LOGGER.log(Level.INFO, "已迁移{0}本图书", booksCount);
|
|
|
|
|
|
|
|
|
|
// 2. 迁移用户数据
|
|
|
|
|
int usersCount = migrateUsers();
|
|
|
|
|
LOGGER.info("已迁移" + usersCount + "个用户");
|
|
|
|
|
LOGGER.log(Level.INFO, "已迁移{0}个用户", usersCount);
|
|
|
|
|
|
|
|
|
|
// 3. 迁移借阅记录
|
|
|
|
|
int loansCount = migrateLoans();
|
|
|
|
|
LOGGER.info("已迁移" + loansCount + "条借阅记录");
|
|
|
|
|
LOGGER.log(Level.INFO, "已迁移{0}条借阅记录", loansCount);
|
|
|
|
|
|
|
|
|
|
// 4. 验证数据完整性
|
|
|
|
|
boolean isValid = validateDataIntegrity();
|
|
|
|
|
@ -68,7 +74,7 @@ public class DatabaseMigrationTool {
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
} catch (SQLException e) {
|
|
|
|
|
LOGGER.log(Level.SEVERE, "数据库迁移失败: " + e.getMessage(), e);
|
|
|
|
|
LOGGER.log(Level.SEVERE, () -> "数据库迁移失败: " + e.getMessage());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -161,23 +167,23 @@ public class DatabaseMigrationTool {
|
|
|
|
|
Statement selectStmt = sourceConn.createStatement();
|
|
|
|
|
PreparedStatement insertStmt = targetConn.prepareStatement(insertSQL)) {
|
|
|
|
|
|
|
|
|
|
ResultSet rs = selectStmt.executeQuery(selectSQL);
|
|
|
|
|
|
|
|
|
|
while (rs.next()) {
|
|
|
|
|
mapper.map(rs, insertStmt);
|
|
|
|
|
insertStmt.addBatch();
|
|
|
|
|
count++;
|
|
|
|
|
try (ResultSet rs = selectStmt.executeQuery(selectSQL)) {
|
|
|
|
|
while (rs.next()) {
|
|
|
|
|
mapper.map(rs, insertStmt);
|
|
|
|
|
insertStmt.addBatch();
|
|
|
|
|
count++;
|
|
|
|
|
|
|
|
|
|
// 每1000条记录执行一次批处理
|
|
|
|
|
if (count % 1000 == 0) {
|
|
|
|
|
insertStmt.executeBatch();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 每1000条记录执行一次批处理
|
|
|
|
|
if (count % 1000 == 0) {
|
|
|
|
|
// 执行剩余的批处理
|
|
|
|
|
if (count % 1000 != 0) {
|
|
|
|
|
insertStmt.executeBatch();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 执行剩余的批处理
|
|
|
|
|
if (count % 1000 != 0) {
|
|
|
|
|
insertStmt.executeBatch();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return count;
|
|
|
|
|
@ -190,22 +196,10 @@ public class DatabaseMigrationTool {
|
|
|
|
|
*/
|
|
|
|
|
public boolean validateDataIntegrity() {
|
|
|
|
|
try {
|
|
|
|
|
// 验证记录数量
|
|
|
|
|
boolean countValid = validateRecordCounts();
|
|
|
|
|
if (!countValid) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 验证外键关联
|
|
|
|
|
boolean fkValid = validateForeignKeys();
|
|
|
|
|
if (!fkValid) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
// 验证记录数量和外键关联
|
|
|
|
|
return validateRecordCounts() && validateForeignKeys();
|
|
|
|
|
} catch (SQLException e) {
|
|
|
|
|
LOGGER.log(Level.SEVERE, "数据完整性验证失败: " + e.getMessage());
|
|
|
|
|
LOGGER.log(Level.SEVERE, () -> "数据完整性验证失败: " + e.getMessage());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -217,14 +211,15 @@ public class DatabaseMigrationTool {
|
|
|
|
|
* @throws SQLException 验证失败时抛出
|
|
|
|
|
*/
|
|
|
|
|
private boolean validateRecordCounts() throws SQLException {
|
|
|
|
|
String[] tables = {"books", "users", "loans"};
|
|
|
|
|
String[] tables = {TABLE_BOOKS, TABLE_USERS, TABLE_LOANS};
|
|
|
|
|
|
|
|
|
|
for (String table : tables) {
|
|
|
|
|
int sourceCount = getRecordCount(sourceAdapter, table);
|
|
|
|
|
int targetCount = getRecordCount(targetAdapter, table);
|
|
|
|
|
|
|
|
|
|
if (sourceCount != targetCount) {
|
|
|
|
|
LOGGER.severe("表" + table + "记录数不一致: 源=" + sourceCount + ", 目标=" + targetCount);
|
|
|
|
|
LOGGER.log(Level.SEVERE, "表{0}记录数不一致: 源={1}, 目标={2}",
|
|
|
|
|
new Object[]{table, sourceCount, targetCount});
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -253,17 +248,19 @@ public class DatabaseMigrationTool {
|
|
|
|
|
Statement stmt = conn.createStatement()) {
|
|
|
|
|
|
|
|
|
|
// 检查book_id外键
|
|
|
|
|
ResultSet rs1 = stmt.executeQuery(checkBookFK);
|
|
|
|
|
if (rs1.next() && rs1.getInt("count") > 0) {
|
|
|
|
|
LOGGER.severe("发现无效的book_id外键");
|
|
|
|
|
return false;
|
|
|
|
|
try (ResultSet rs1 = stmt.executeQuery(checkBookFK)) {
|
|
|
|
|
if (rs1.next() && rs1.getInt(COLUMN_COUNT) > 0) {
|
|
|
|
|
LOGGER.severe("发现无效的book_id外键");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查user_id外键
|
|
|
|
|
ResultSet rs2 = stmt.executeQuery(checkUserFK);
|
|
|
|
|
if (rs2.next() && rs2.getInt("count") > 0) {
|
|
|
|
|
LOGGER.severe("发现无效的user_id外键");
|
|
|
|
|
return false;
|
|
|
|
|
try (ResultSet rs2 = stmt.executeQuery(checkUserFK)) {
|
|
|
|
|
if (rs2.next() && rs2.getInt(COLUMN_COUNT) > 0) {
|
|
|
|
|
LOGGER.severe("发现无效的user_id外键");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -292,7 +289,7 @@ public class DatabaseMigrationTool {
|
|
|
|
|
ResultSet rs = stmt.executeQuery(sql)) {
|
|
|
|
|
|
|
|
|
|
if (rs.next()) {
|
|
|
|
|
return rs.getInt("count");
|
|
|
|
|
return rs.getInt(COLUMN_COUNT);
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
@ -304,9 +301,9 @@ public class DatabaseMigrationTool {
|
|
|
|
|
*/
|
|
|
|
|
private String getCountSqlForTable(String tableName) {
|
|
|
|
|
return switch (tableName) {
|
|
|
|
|
case "books" -> "SELECT COUNT(*) as count FROM books";
|
|
|
|
|
case "users" -> "SELECT COUNT(*) as count FROM users";
|
|
|
|
|
case "loans" -> "SELECT COUNT(*) as count FROM loans";
|
|
|
|
|
case TABLE_BOOKS -> "SELECT COUNT(*) as count FROM books";
|
|
|
|
|
case TABLE_USERS -> "SELECT COUNT(*) as count FROM users";
|
|
|
|
|
case TABLE_LOANS -> "SELECT COUNT(*) as count FROM loans";
|
|
|
|
|
default -> throw new IllegalArgumentException("非法表名: " + tableName);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
@ -318,7 +315,7 @@ public class DatabaseMigrationTool {
|
|
|
|
|
* @return 导出是否成功
|
|
|
|
|
*/
|
|
|
|
|
public boolean exportToSQL(String outputFile) {
|
|
|
|
|
LOGGER.info("正在导出数据到SQL文件: " + outputFile);
|
|
|
|
|
LOGGER.log(Level.INFO, "正在导出数据到SQL文件: {0}", outputFile);
|
|
|
|
|
|
|
|
|
|
try (FileWriter writer = new FileWriter(outputFile);
|
|
|
|
|
Connection conn = sourceAdapter.getConnection()) {
|
|
|
|
|
@ -329,19 +326,19 @@ public class DatabaseMigrationTool {
|
|
|
|
|
writer.write("-- 源数据库: " + sourceAdapter.getDatabaseType() + "\n\n");
|
|
|
|
|
|
|
|
|
|
// 导出books表
|
|
|
|
|
exportTable(conn, writer, "books");
|
|
|
|
|
exportTable(conn, writer, TABLE_BOOKS);
|
|
|
|
|
|
|
|
|
|
// 导出users表
|
|
|
|
|
exportTable(conn, writer, "users");
|
|
|
|
|
exportTable(conn, writer, TABLE_USERS);
|
|
|
|
|
|
|
|
|
|
// 导出loans表
|
|
|
|
|
exportTable(conn, writer, "loans");
|
|
|
|
|
exportTable(conn, writer, TABLE_LOANS);
|
|
|
|
|
|
|
|
|
|
LOGGER.info("数据导出完成");
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
} catch (IOException | SQLException e) {
|
|
|
|
|
LOGGER.log(Level.SEVERE, "数据导出失败: " + e.getMessage());
|
|
|
|
|
LOGGER.log(Level.SEVERE, () -> "数据导出失败: " + e.getMessage());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -402,9 +399,9 @@ public class DatabaseMigrationTool {
|
|
|
|
|
*/
|
|
|
|
|
private String getSelectSqlForTable(String tableName) {
|
|
|
|
|
return switch (tableName) {
|
|
|
|
|
case "books" -> "SELECT * FROM books";
|
|
|
|
|
case "users" -> "SELECT * FROM users";
|
|
|
|
|
case "loans" -> "SELECT * FROM loans";
|
|
|
|
|
case TABLE_BOOKS -> "SELECT * FROM books";
|
|
|
|
|
case TABLE_USERS -> "SELECT * FROM users";
|
|
|
|
|
case TABLE_LOANS -> "SELECT * FROM loans";
|
|
|
|
|
default -> throw new IllegalArgumentException("非法表名: " + tableName);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
@ -416,7 +413,7 @@ public class DatabaseMigrationTool {
|
|
|
|
|
* @return 导入是否成功
|
|
|
|
|
*/
|
|
|
|
|
public boolean importFromSQL(String inputFile) {
|
|
|
|
|
LOGGER.info("正在从SQL文件导入数据: " + inputFile);
|
|
|
|
|
LOGGER.log(Level.INFO, "正在从SQL文件导入数据: {0}", inputFile);
|
|
|
|
|
|
|
|
|
|
try (BufferedReader reader = new BufferedReader(new FileReader(inputFile));
|
|
|
|
|
Connection conn = targetAdapter.getConnection();
|
|
|
|
|
@ -440,11 +437,11 @@ public class DatabaseMigrationTool {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOGGER.info("数据导入完成,共执行" + count + "条SQL语句");
|
|
|
|
|
LOGGER.log(Level.INFO, "数据导入完成,共执行{0}条SQL语句", count);
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
} catch (IOException | SQLException e) {
|
|
|
|
|
LOGGER.log(Level.SEVERE, "数据导入失败: " + e.getMessage());
|
|
|
|
|
LOGGER.log(Level.SEVERE, () -> "数据导入失败: " + e.getMessage());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|