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),用于迁移前备份。 * *

用法: *

 *   java SchemaDump <jdbc-url>                      # 全部表的结构
 *   java SchemaDump <jdbc-url> --data=t1,t2         # 指定表的数据
 * 
*/ 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 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 tables(Connection connection) throws Exception { List 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(); } }