yeg
2026-09-16 e617e479055d37e7482621d46dfa8608ddb55f07
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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 &lt;jdbc-url&gt;                      # 全部表的结构
 *   java SchemaDump &lt;jdbc-url&gt; --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();
    }
}