Java 應用常需呼叫外部批次檔執行資料庫維護腳本 (如 SQL*Plus 執行 .sql)。本文提供完整範例:Runtime.getRuntime().exec() 呼叫 cmd.exe /C start /b 背景執行 .bat、waitFor() 同步阻塞、exitValue() 判斷成敗、異常處理與日誌輸出。
檔案結構
D:\sqlplus\
├── test.sql -- SQL 腳本
└── sql.bat -- 批次啟動器
SQL 腳本 test.sql
insert into ss values(11);
insert into ss values(12);
insert into ss values(13);
commit;
exit;
批次檔 sql.bat
@ECHO OFF
sqlplus -s username/password@sid @D:\sqlplus\test.sql
exit
-s:靜默模式 (隱藏版權資訊)。
@:指定腳本檔路徑。
- 需確保
sqlplus 在系統 PATH 中,或使用完整路徑。
Java 完整程式碼
package com.tdxx.sqlplus;
import java.io.IOException;
public class ExecComm {
public static void main(String[] args) {
System.out.println("開始執行.");
runBat();
}
public static void runBat() {
Runtime rt = Runtime.getRuntime();
Process ps = null;
try {
// cmd.exe /C 執行命令後結束
// start /b 不開新視窗、背景執行
ps = rt.exec("cmd.exe /C start /b D:\\sqlplus\\sql.bat");
ps.waitFor(); // 同步等待結束
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt(); // 重設中斷旗標
} catch (IOException e) {
e.printStackTrace();
}
int exitCode = ps.exitValue(); // 非零即異常
if (exitCode == 0) {
System.out.println("執行完成.");
} else {
System.err.println("執行失敗,退出碼: " + exitCode);
}
}
}
關鍵 API 解析
| 方法 | 用途 |
Runtime.exec(String) | 啟動子進程,回傳 Process 控制代碼 |
Process.waitFor() | 阻塞當前執行緒直到子進程終止 |
Process.exitValue() | 取得退出碼 (0=成功),若進程未結束拋 IllegalThreadStateException |
Process.getInputStream() | 讀取子進程標準輸出 (若需擷取輸出) |
Process.getErrorStream() | 讀取子進程標準錯誤 |
進階改良:擷取輸出與超時控制 (Java 8+)
ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/C", "D:\\sqlplus\\sql.bat");
pb.redirectErrorStream(true); // 合併 stderr 到 stdout
Process ps = pb.start();
// 非同步讀取輸出避免緩衝區滿死鎖
new Thread(() -> {
try (BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()))) {
String line;
while ((line = br.readLine()) != null) System.out.println("[BAT] " + line);
} catch (IOException e) { e.printStackTrace(); }
}).start();
// Java 8: waitFor(long, TimeUnit) 支援超時
boolean finished = ps.waitFor(60, TimeUnit.SECONDS);
if (!finished) {
ps.destroyForcibly();
System.err.println("執行超時,強制終止");
}
int code = ps.exitValue();
常見坑與對策
| 現象 | 原因 | 解決 |
| 程式卡住不返回 | 子進程輸出緩衝區滿,未讀取 | 必須非同步讀取 getInputStream()/getErrorStream(),或用 ProcessBuilder.redirectErrorStream(true) |
| 找不到 sqlplus | PATH 未包含 Oracle Client bin 目錄 | 批次檔使用完整路徑 D:\oracle\bin\sqlplus.exe |
| 退出碼永遠為 0 | 批次檔未正確傳遞 sqlplus 退出碼 | 批次檔末尾加 exit /b %ERRORLEVEL% |
| 中文亂碼 | 主控台編碼與 Java 預設編碼不符 | new InputStreamReader(is, "MS950") (繁中) 或 GBK (簡中) |
安全性提醒
- 避免在命令字串中拼接未驗證的使用者輸入 (命令注入風險)。
- 優先使用
ProcessBuilder 陣列形式傳參,自動處理引號跳脫。
- 生產環境考慮改用 JDBC 直接執行 SQL,減少外部相依與部署複雜度。
掌握 Runtime.exec / ProcessBuilder 正確用法,即能穩健整合外部腳本與工具。
- 上一篇文章: Python 多行註解與文件字串完整指南:三引號、單行 #、IDE 折疊區塊與最佳實務
- 下一篇文章: Java Excel 轉 PDF 實戰:Jacob COM 呼叫 Office 自動化完整方案與部署注意事項
Copyright ©
程式碼之家 All Rights Reserved