需求:Oracle 定時任務調用第三方工具 sqluldr2 高效導出大量數據 → WinRAR 壓縮加密 → 發送郵件。純 PL/SQL 無法直接執行 OS 命令,利用 **Java Stored Procedure (JSP)** 橋接 JVM 與作業系統。
[DBMS_SCHEDULER] → [PL/SQL Wrapper] → [Java Source: jv_run_extpro] → [OS Process: sqluldr2 / WinRAR]
↓
[StreamGobbler 非同步讀取 stdout/stderr]
↓
[寫入 DBMS_OUTPUT / 日誌表]
CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED jv_run_extpro AS
import java.io.*;
import java.lang.*;
import java.util.*;
public class jv_run_extpro {
public static void run(String cmd) throws IOException {
Process p = Runtime.getRuntime().exec(cmd);
// 關鍵:非同步消耗 stdout/stderr,防止緩衝區填滿導致進程掛起
StreamGobbler errorGobbler = new StreamGobbler(p.getErrorStream(), "ERROR");
StreamGobbler outputGobbler = new StreamGobbler(p.getInputStream(), "OUTPUT");
errorGobbler.start();
outputGobbler.start();
try { p.waitFor(); }
catch (InterruptedException ie) { System.out.println(ie); }
}
static class StreamGobbler extends Thread {
InputStream is; String type;
StreamGobbler(InputStream is, String type) { this.is = is; this.type = type; }
public void run() {
try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(type.equals("ERROR") ? "ERROR: " + line : "DEBUG: " + line);
}
} catch (IOException ioe) { ioe.printStackTrace(); }
}
}
};
CREATE OR REPLACE PROCEDURE pro_jv_run_extpro(p_cmd VARCHAR2) AS LANGUAGE JAVA NAME 'jv_run_extpro.run(java.lang.String)';
-- 1. 授予 Java 權限 (需 SYS 執行)
BEGIN
DBMS_JAVA.GRANT_PERMISSION('APP_USER', 'SYS:java.io.FilePermission', '<>', 'read,write,execute');
DBMS_JAVA.GRANT_PERMISSION('APP_USER', 'SYS:java.lang.RuntimePermission', 'writeFileDescriptor', '');
DBMS_JAVA.GRANT_PERMISSION('APP_USER', 'SYS:java.lang.RuntimePermission', 'readFileDescriptor', '');
COMMIT;
END;
/
-- 2. 確保 JVM 已載入
SELECT * FROM DBA_REGISTRY WHERE COMP_ID='JAVAVM'; -- STATUS=VALID
BEGIN
-- 1. sqluldr2 導出 (高性能無需 SQL*Net 開銷)
pro_jv_run_extpro('sqluldr264.exe scott/tiger@pdborcl query="select * from big_table" field=, head=yes file=D:\export\big_table.csv');
-- 2. WinRAR 壓縮加密
pro_jv_run_extpro('"C:\Program Files\WinRAR\Rar.exe" a -ep -hpMySecretPass "D:\export\big_table.rar" "D:\export\big_table.csv"');
-- 3. 可擴展:調用 PowerShell 發送郵件
-- pro_jv_run_extpro('powershell -Command "Send-MailMessage ..."');
END;
/
| 點 | 說明 | 避坑指南 |
|---|---|---|
| StreamGobbler 必須 | OS 進程 stdout/stderr 管道容量有限(~4KB),不讀取會阻塞進程 | **絕不可省略**,生產環境建議寫入資料表而非 DBMS_OUTPUT |
| 路徑含空格 | Windows 路徑需雙引號包裹 | Java 字串中轉義:\"C:\\Program Files\\... |
| 工作目錄 | 預設為 $ORACLE_HOME/dbs | 建議命令使用絕對路徑,或先 cd /d D:\export && sqluldr2... |
| 環境變數繼承 | 繼承啟動 Oracle 服務的 OS 環境 | 若需特定 PATH,在命令中完整指定可執行檔路徑 |
| 執行身份 | Oracle 服務帳號 (Windows: 通常為 LocalSystem) | 確保該帳號有讀寫目標目錄、執行工具權限 |
| 超時控制 | Java 無內建超時 | PL/SQL 層用 DBMS_SCHEDULER 設定 MAX_RUN_DURATION 或另開執行緒監控 |
-- 修改 Java 回傳 int
public static int run(String cmd) throws IOException {
Process p = Runtime.getRuntime().exec(cmd);
new StreamGobbler(p.getErrorStream(), "ERR").start();
new StreamGobbler(p.getInputStream(), "OUT").start();
return p.waitFor(); // 0=成功, 非0=失敗
}
-- PL/SQL 對應修改
LANGUAGE JAVA NAME 'jv_run_extpro.run(java.lang.String) return int';
DBMS_JAVA.GRANT_PERMISSION(..., 'D:\export\*', ...)Java Stored Procedure 是 Oracle 突破「資料庫隔離 OS」的標準橋樑。**StreamGobbler 非同步讀取是穩定運行的核心**,權限最小化是安全底線。適用於:ETL 工具鏈整合、報表生成、檔案轉換、外部 API 調用等場景。若僅需簡單檔案操作,優先考慮 UTL_FILE、DBMS_SCHEDULER 外部作業、或 Oracle 19c+ DBMS_CLOUD 等原生方案。