import java.sql.Connection;
|
import java.sql.DriverManager;
|
import java.sql.PreparedStatement;
|
import java.sql.ResultSet;
|
import java.sql.Statement;
|
import java.util.ArrayList;
|
import java.util.List;
|
import java.util.StringJoiner;
|
|
/**
|
* 只读探针:导出某个库的结构(SHOW CREATE TABLE)或指定表的数据(INSERT),用于迁移前备份。
|
*
|
* <p>用法:
|
* <pre>
|
* java SchemaDump <jdbc-url> # 全部表的结构
|
* java SchemaDump <jdbc-url> --data=t1,t2 # 指定表的数据
|
* </pre>
|
*/
|
public class SchemaDump {
|
|
public static void main(String[] args) throws Exception {
|
String url = args[0];
|
String dataTables = null;
|
for (String arg : args) {
|
if (arg.startsWith("--data=")) dataTables = arg.substring("--data=".length());
|
}
|
try (Connection connection = DriverManager.getConnection(url, "root", "123456")) {
|
if (dataTables == null) {
|
dumpSchema(connection);
|
} else {
|
for (String table : dataTables.split(",")) {
|
dumpData(connection, table.trim());
|
}
|
}
|
}
|
}
|
|
private static void dumpSchema(Connection connection) throws Exception {
|
System.out.println("-- 结构备份:" + connection.getCatalog() + ",导出时间 " + java.time.LocalDateTime.now());
|
System.out.println("-- 还原方式:在空库里按顺序执行本文件(表按名称排序,先建表后加外键由 MySQL 自行处理)。");
|
System.out.println("SET FOREIGN_KEY_CHECKS = 0;");
|
for (String table : tables(connection)) {
|
try (Statement statement = connection.createStatement();
|
ResultSet rows = statement.executeQuery("show create table `" + table + "`")) {
|
rows.next();
|
System.out.println();
|
System.out.println("-- " + table);
|
System.out.println("DROP TABLE IF EXISTS `" + table + "`;");
|
System.out.println(rows.getString(2) + ";");
|
}
|
}
|
System.out.println();
|
System.out.println("SET FOREIGN_KEY_CHECKS = 1;");
|
}
|
|
private static void dumpData(Connection connection, String table) throws Exception {
|
System.out.println();
|
System.out.println("-- 数据备份 " + table + "(导出时间 " + java.time.LocalDateTime.now() + ")");
|
try (Statement statement = connection.createStatement();
|
ResultSet rows = statement.executeQuery("select * from `" + table + "`")) {
|
int columns = rows.getMetaData().getColumnCount();
|
List<String> names = new ArrayList<>();
|
for (int index = 1; index <= columns; index++) names.add(rows.getMetaData().getColumnLabel(index));
|
int count = 0;
|
while (rows.next()) {
|
StringJoiner values = new StringJoiner(", ");
|
for (int index = 1; index <= columns; index++) {
|
values.add(literal(rows.getObject(index)));
|
}
|
System.out.println("INSERT INTO `" + table + "` (`" + String.join("`, `", names) + "`) VALUES ("
|
+ values + ");");
|
count++;
|
}
|
System.out.println("-- " + table + " 共 " + count + " 行");
|
}
|
}
|
|
private static List<String> tables(Connection connection) throws Exception {
|
List<String> tables = new ArrayList<>();
|
try (PreparedStatement statement = connection.prepareStatement("""
|
select table_name from information_schema.tables
|
where table_schema = ? and table_type = 'BASE TABLE' order by table_name
|
""")) {
|
statement.setString(1, connection.getCatalog());
|
try (ResultSet rows = statement.executeQuery()) {
|
while (rows.next()) tables.add(rows.getString(1));
|
}
|
}
|
return tables;
|
}
|
|
/** Java 17 已经移除 javax.xml.bind,自带一个十六进制编码。 */
|
private static String hex(byte[] bytes) {
|
StringBuilder text = new StringBuilder(bytes.length * 2);
|
for (byte value : bytes) text.append(String.format("%02X", value));
|
return text.toString();
|
}
|
|
/** 一个够用的字面量转义:NULL、数字裸写、其余加引号并转义控制字符。 */
|
private static String literal(Object value) {
|
if (value == null) return "NULL";
|
if (value instanceof Number || value instanceof Boolean) return String.valueOf(value);
|
if (value instanceof byte[] bytes) return "0x" + hex(bytes);
|
String text = String.valueOf(value);
|
StringBuilder escaped = new StringBuilder("'");
|
for (char character : text.toCharArray()) {
|
switch (character) {
|
case '\'' -> escaped.append("''");
|
case '\\' -> escaped.append("\\\\");
|
case '\n' -> escaped.append("\\n");
|
case '\r' -> escaped.append("\\r");
|
case '\0' -> escaped.append("\\0");
|
case '\u001a' -> escaped.append("\\Z");
|
default -> escaped.append(character);
|
}
|
}
|
return escaped.append("'").toString();
|
}
|
}
|