import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.Statement; import java.util.ArrayList; import java.util.List; import java.util.Scanner; /** * 数据库直连小工具。用法见 printUsage()。 * 运行:bash sql/db.sh "SELECT 1" */ public class DbTool { private static final String DEFAULT_URL = "jdbc:mysql://localhost:3306/product-inventory-management-rbhb" + "?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull" + "&serverTimezone=GMT%2B8&sslMode=DISABLED&allowPublicKeyRetrieval=true"; private static final String DEFAULT_USER = "root"; private static final String DEFAULT_PASS = "123456"; public static void main(String[] args) throws Exception { String url = DEFAULT_URL; String user = DEFAULT_USER; String pass = DEFAULT_PASS; String sqlText = null; Path scriptFile = null; List positional = new ArrayList<>(); for (int i = 0; i < args.length; i++) { String arg = args[i]; switch (arg) { case "-url" -> url = args[++i]; case "-user" -> user = args[++i]; case "-pass" -> pass = args[++i]; case "-f" -> scriptFile = Path.of(args[++i]); case "-h", "--help" -> { printUsage(); return; } default -> positional.add(arg); } } if (scriptFile != null) { sqlText = Files.readString(scriptFile, StandardCharsets.UTF_8); } else if (!positional.isEmpty()) { sqlText = String.join(" ", positional); } else if (System.console() != null) { printUsage(); return; } else { Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8).useDelimiter("\\A"); sqlText = scanner.hasNext() ? scanner.next() : null; } if (sqlText == null || sqlText.isBlank()) { printUsage(); return; } List statements = splitStatements(sqlText); if (statements.isEmpty()) { printUsage(); return; } try (Connection conn = DriverManager.getConnection(url, user, pass)) { for (String sql : statements) { runOne(conn, sql); } } } private static void runOne(Connection conn, String sql) throws Exception { long start = System.currentTimeMillis(); try (Statement stmt = conn.createStatement()) { boolean hasResultSet = stmt.execute(sql); long cost = System.currentTimeMillis() - start; if (hasResultSet) { try (ResultSet rs = stmt.getResultSet()) { printResultSet(rs); } System.out.println("耗时 " + cost + " ms"); } else { System.out.println("受影响行数: " + stmt.getUpdateCount() + " 耗时 " + cost + " ms"); } } catch (Exception e) { System.out.println("执行失败: " + e.getMessage()); System.out.println("SQL: " + sql); throw e; } } private static void printResultSet(ResultSet rs) throws Exception { ResultSetMetaData meta = rs.getMetaData(); int columnCount = meta.getColumnCount(); String[] headers = new String[columnCount]; int[] widths = new int[columnCount]; for (int i = 0; i < columnCount; i++) { headers[i] = meta.getColumnLabel(i + 1); widths[i] = displayWidth(headers[i]); } List rows = new ArrayList<>(); while (rs.next()) { String[] row = new String[columnCount]; for (int i = 0; i < columnCount; i++) { Object value = rs.getObject(i + 1); row[i] = value == null ? "NULL" : String.valueOf(value); widths[i] = Math.max(widths[i], displayWidth(row[i])); } rows.add(row); } StringBuilder separator = new StringBuilder(); for (int width : widths) { separator.append('-').append("-".repeat(width)).append('-'); } System.out.println(separator); System.out.println(joinRow(headers, widths)); System.out.println(separator); for (String[] row : rows) { System.out.println(joinRow(row, widths)); } System.out.println(separator); System.out.println("共 " + rows.size() + " 行"); } private static String joinRow(String[] cells, int[] widths) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < cells.length; i++) { sb.append("| ").append(cells[i]); sb.append(" ".repeat(Math.max(0, widths[i] - displayWidth(cells[i]) + 1))); } return sb.append('|').toString(); } /** * 全角字符占两个终端宽度,按 2 计算以保证表格对齐。 */ private static int displayWidth(String text) { int width = 0; for (int i = 0; i < text.length(); i++) { width += text.charAt(i) > 0x2E80 ? 2 : 1; } return width; } /** * 按分号切分语句,忽略空语句与纯注释行。 * 简单实现:不支持字符串字面量中包含分号的极端场景。 */ private static List splitStatements(String sqlText) { List statements = new ArrayList<>(); for (String part : sqlText.split(";")) { String trimmed = part.trim(); if (trimmed.isEmpty() || isCommentOnly(trimmed)) { continue; } statements.add(trimmed); } return statements; } private static boolean isCommentOnly(String sql) { for (String line : sql.split("\\R")) { String stripped = line.trim(); if (!stripped.isEmpty() && !stripped.startsWith("--") && !stripped.startsWith("#")) { return false; } } return true; } private static void printUsage() { System.out.println(""" 数据库直连工具 用法: bash sql/db.sh "SQL语句" bash sql/db.sh -f 脚本.sql echo "SQL语句" | bash sql/db.sh 可覆盖连接参数: -url jdbc:mysql://host:3306/db?... -user root -pass 123456 """); } }