SQLJ (SQL Embedded in Java) 為 **靜態 SQL 嵌入 Java** 的標準 (ISO/ANSI),提供:**編譯期 SQL 語法/型別檢查**、**效能優於動態 JDBC** (預先綁定存取路徑)、**程式碼可讀性優於 PL/SQL** (Java 生態、OOP、豐富函式庫)。Oracle 8i/9i/10g/11g/12c/19c/21c 均支援 SQLJ (資料庫內 JVM - Aurora/JServer)。
業務需求:依期間刪除 Classification 子表中屬於該期間 ValidTransaction 父表的所有明細列 (百萬列級),分批提交、統計刪除比例、超過閾值自動重建索引。
// PipelineReset.sqlj
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Iterator;
import oracle.jdbc.driver.OracleConnection;
import sqlj.runtime.ref.DefaultContext;
import sqlj.runtime.ExecutionContext;
// 迭代器宣告 (對應 PL/SQL CURSOR)
#sql iterator StartEndDate (Timestamp startDate, Timestamp endDate);
#sql iterator ValidTransactions (Long transactionId);
#sql iterator ClassificationCount (long);
public class PipelineReset {
public static void DeleteClassification(
String periodName,
int commitBatchSize,
int percentageDeleted
) throws SQLException {
// 1. 取得期間起迄
StartEndDate dateIter;
#sql dateIter = { SELECT startDate, endDate FROM Period WHERE description = :periodName };
Timestamp startDate = null, endDate = null;
if (dateIter.next()) { startDate = dateIter.startDate(); endDate = dateIter.endDate(); }
dateIter.close();
// 2. 取得該期間所有交易 ID
ValidTransactions vtIter;
#sql vtIter = {
SELECT T.TransactionId
FROM ValidTransaction T
WHERE T.TransactionDate >= :startDate
AND T.TransactionDate <= :endDate
AND EXISTS (SELECT 1 FROM Classification C WHERE C.TransactionId = T.TransactionId)
};
ArrayList vtIdList = new ArrayList<>();
while (vtIter.next()) vtIdList.add(vtIter.transactionId());
vtIter.close();
// 3. 批次刪除
ExecutionContext ec = DefaultContext.getDefaultContext().getExecutionContext();
ClassificationCount countIter;
long vDeletedRows = 0, vTotalRows = 0;
int vCount = 0;
if (!vtIdList.isEmpty()) {
#sql countIter = { SELECT COUNT(*) FROM Classification };
#sql { FETCH :countIter INTO :vTotalRows };
countIter.close();
Iterator itr = vtIdList.iterator();
while (itr.hasNext()) {
vCount++;
#sql [ec] { DELETE FROM Classification WHERE TransactionId = :(itr.next()) };
vDeletedRows += ec.getUpdateCount();
if (vCount % commitBatchSize == 0) {
#sql [ec] { COMMIT };
}
}
#sql [ec] { COMMIT };
}
// 4. 判斷是否重建索引
if ((((float) vDeletedRows / (float) vTotalRows) * 100) >= percentageDeleted) {
#sql [ec] { CALL RebuildIndex('CLASSIFICATION_PK_IND', 'parallel nologging compute statistics') };
}
}
}
# sqlj.properties (連線/選項集中管理)
sqlj.user=scott
sqlj.password=tiger
sqlj.driver=oracle.jdbc.driver.OracleDriver
sqlj.url=jdbc:oracle:oci8:@ORCL
sqlj.warn=verbose
sqlj.linemap=true
# 翻譯 + 編譯 + Profile 客製化
sqlj -props=sqlj.properties -explain -ser2class -status PipelineReset.sqlj
# 產生:PipelineReset.class, StartEndDate.class, ValidTransactions.class, ClassificationCount.class, PipelineReset_SJProfile*.class
jar cvf0 PipelineReset.jar *.class
loadjava -user scott/tiger@ORCL -resolve PipelineReset.jar
# 或 Thin Driver: loadjava -thin -user scott/tiger@host:1521:ORCL -resolve PipelineReset.jar
驗證:SELECT object_name, status FROM user_objects WHERE object_type = 'JAVA CLASS' ORDER BY object_name;全部VALID即成功。
CREATE OR REPLACE PACKAGE PipelineResetFull AS
PROCEDURE JDeleteClassification (
periodName IN VARCHAR2,
commitBatchSize IN NUMBER,
percentDeleted IN NUMBER
);
END PipelineResetFull;
/
CREATE OR REPLACE PACKAGE BODY PipelineResetFull AS
PROCEDURE JDeleteClassification (
periodName IN VARCHAR2,
commitBatchSize IN NUMBER,
percentDeleted IN NUMBER
) AS
LANGUAGE JAVA
NAME 'PipelineReset.DeleteClassification(java.lang.String, int, int)'; -- 必須完整簽名
END JDeleteClassification;
END PipelineResetFull;
/
關鍵坑:`NAME` 子句**必須使用完整 Java 型別名稱** (`java.lang.String`),不可寫 `String`,否則運行時拋 ORA-29531: no method ... in class ...。
BEGIN
PipelineResetFull.JDeleteClassification('January 1999', 10000, 10);
END;
/
-- 等同 PL/SQL 版本呼叫,完全整合,呼叫端無感差異
| 維度 | SQLJ | PL/SQL | JDBC (Dynamic) |
|---|---|---|---|
| SQL 檢查時機 | 編譯期 (翻譯器語義檢查) | 編譯期 (PL/SQL 編譯器) | 執行期 (PreparedStatement) |
| 型別安全 | 強 (Java 型別映射) | 強 (PL/SQL 型別) | 弱 (setObject/getObject) |
| 效能 | 高 (預先綁定、批次、預取) | 高 (原生、BULK COLLECT/FORALL) | 中 (每次解析/綁定,除非 Statement Caching) |
| 程式碼結構 | Java OOP、模組化、測試友善 | 程序式、包/函數/程序 | Java 字串拼接/參數化 |
| 動態 SQL | Oracle 9i+ 支援 (#sql { ... } 動態) | 原生 EXECUTE IMMEDIATE | 原生 |
| 部署複雜度 | 較高 (翻譯→編譯→JAR→loadjava→Wrapper) | 低 (直接 CREATE PROCEDURE) | 低 (純 Java 應用層) |
| 除錯 | JDeveloper / Eclipse + SQLJ Debugger | PL/SQL Debugger (SQL Developer) | Java Debugger |
SQLJ 以 **編譯期安全、高效能、Java 生態** 三大優勢,成為 PL/SQL 之外的強力選項,特別適合**演算法複雜、需跨平台邏輯重用、團隊 Java 技能強** 的場景。掌握「翻譯→編譯→JAR→loadjava→Wrapper」五步驟,即可在 Oracle 資料庫內無縫運行 Java 邏輯,享受兩全其美的開發體驗。