現代企業級應用程式通常部署在多種資料庫平台上,如 Oracle、DB2、Sybase、SQL Server、Informix 等。在這樣的異構環境中,程式碼的可移植性與可維護性變得至關重要。傳統上,各資料庫廠商使用專有的程序語言(Oracle 採用 PL/SQL,Sybase 與 SQL Server 採用 Transact-SQL),導致跨平台遷移極為複雜且耗時。
Java 憑藉其跨平台特性與強大的網路能力,成為開發可移植資料庫應用的理想選擇。Oracle 在 8i 版本中整合了 Java 虛擬機(JVM),稱為 JServer(或 Aurora JVM),支援兩種程式設計模型:
在 Oracle 8i 中,Java 可透過內嵌的 JDBC 驅動程式呼叫 SQL 與 PL/SQL;反之,PL/SQL 與 SQL 也能透過 CREATE PROCEDURE AS JAVA 等擴展 DDL 呼叫 Java 程式碼,實現雙向無縫整合。
SQLJ 是一種允許將靜態 SQL 陳述式以文字形式直接嵌入 Java 程式碼的語言。開發人員遵循特定規則編寫 SQLJ 程式,再經由 SQLJ 轉譯器將嵌入式 SQL 轉換為對 SQLJ 執行階段程式庫的呼叫,產出標準的 Java 原始碼,最後由 Java 編譯器編譯為位元組碼。SQLJ 執行階段環境由輕量級的純 Java 程式庫組成,透過相應資料庫的 JDBC 驅動程式與資料庫通訊。
SQLJ 類似於 Oracle Pro*C 等嵌入式 SQL 實作,旨在協助 Java 開發人員建構資料庫應用。它是 ISO 與 ANSI 標準,由 Oracle、IBM、Sybase、Informix 等廠商共同開發支援,確保跨資料庫的相容性。
.sqlj 來源檔案,產生 Java 檔案與二進位描述檔(profile)。必要執行環境:SQLJ 執行階段程式庫、JDBC 驅動程式(如 Oracle Thin、OCI、JDBC-ODBC Bridge 等)、JVM。
SQLJ 採用靜態 SQL 模型,提供強型別的編譯期檢查,不僅驗證 SQL 語法與主機變數型別相容性,還能根據資料庫 Schema 驗證查詢正確性。相比之下,JDBC 採用動態模型,錯誤往往只在執行時發現。SQLJ 程式碼更為精簡,例如單一 #sql 區塊即可完成查詢與結果綁定,而 JDBC 需準備 PreparedStatement、逐一綁定參數、處理 ResultSet。
SQLJ 程式可與 JDBC 程式碼在同一來源檔中互操作,支援動態 SQL 需求;Oracle 9i 更直接在 SQLJ 中支援動態 SQL。
Java 與 PL/SQL 互補而非互斥。Java 儲存程序在數學運算密集型任務上快 5 到 100 倍,且具跨平台可移植性;PL/SQL 則與 Oracle 核心緊密整合,資料型別無需轉換,在 OLTP 與批次處理上分別快 1.5 倍與 2.5 倍,且佔用較少 CPU 資源。
最佳實務:Oracle 提供理想環境結合兩者優勢。PL/SQL 可呼叫 SQLJ/JDBC 程序構建 EJB/CORBA 應用;Java 可透過 JDBC/SQLJ 呼叫 PL/SQL 程序、函數與匿名區塊。例如:
// 呼叫預存程序
#sql { CALL UpdateSalary };
// 呼叫函數
String name;
#sql { name = { VALUES GetName() } };
// 或
#sql { SET :name = GetName() };
// 匿名 PL/SQL 區塊
#sql { BEGIN ... END; };
用於管理資料庫連線。單一連線可使用 DefaultContext 實例,透過 Oracle.connect() 建立;多重連線則使用 Oracle.getConnection() 建立多個 DefaultContext 實例,並在 SQLJ 陳述式中指定上下文:#sql [myContext] { SQL statement };。程式結束時應在 finally 區塊中提交交易並關閉連線。
SQL 查詢結果集以迭代器物件儲存,概念類似 PL/SQL 游標。使用步驟:宣告迭代器類別 → 宣告迭代器物件 → 以 SELECT 陳述式填入資料 → 循環讀取資料 → 關閉迭代器。兩種類型:
empIterator.Ename())讀取資料。FETCH ... INTO 語法與 endFetch() 方法讀取。兩者效能相當,選擇取決於程式碼風格偏好。
在大括號內包含靜態 SQL 操作。不傳回值的陳述式(如 DDL)直接執行;傳回值的陳述式需使用主機變數接收結果,可使用 VALUES 或 SET 運算子呼叫函數。
主機變數是 Java 程式中宣告的變數,嵌入 SQLJ 陳述式中作為主機運算式,實現 Java 與資料庫間的資料交換。支援所有標準 JDBC 型別,Oracle SQLJ 另支援 ROWID、CLOB、BLOB、Object、REF 等 Oracle 專屬型別。
public static void listEmployees(String empSalary) throws SQLException {
EmpIteratorClass empIterator;
Integer salary = new Integer(empSalary);
try {
#sql empIterator = {
select Ename, Job, HireDate
from Emp
where Sal > :salary
};
while (empIterator.next()) {
System.out.println("Name: " + empIterator.Ename());
System.out.println("Job: " + empIterator.Job());
System.out.println("Hire Date: " + empIterator.HireDate().toString());
}
empIterator.close();
} catch (SQLException e) {
System.err.println("SQLException: " + e);
System.exit(1);
}
}
public static void listEmployees(String empSalary) throws SQLException {
EmpIteratorClass empIterator;
Integer salary = new Integer(empSalary);
String name = null, job = null;
Timestamp hireDate = null;
try {
#sql empIterator = {
select Ename, Job, HireDate
from Emp
where Sal > :salary
};
while (true) {
#sql { FETCH :empIterator INTO :name, :job, :hireDate };
if (empIterator.endFetch()) break;
System.out.println("Name: " + name);
System.out.println("Job: " + job);
System.out.println("Hire Date: " + hireDate.toString());
}
empIterator.close();
} catch (SQLException e) {
System.err.println("SQLException: " + e);
System.exit(1);
}
}