級別: 初級
Connie Tsui, DB2 解決方案集成團隊, IBM 多倫多實驗室
2003 年 2 月 01 日
了解為什么 SQLJ 是注重安全性、性能和簡單性的開發人員所選擇的語言。其中還包含了代碼樣本。
簡介
使用 Java? 訪問關系數據的標準方法有兩種:SQLJ 和 JDBC?。對于 IBM® DB2® Universal Database?(UDB)應用程序,為什么應該考慮 SQLJ 呢?這是因為當應用程序員要考慮安全性、性能和簡單性時,往往會選擇 SQLJ 這樣的語言。本文向您介紹了有關 SQLJ 的一些背景,討論了相對于 JDBC,SQLJ 所具有的優勢,還特別指出了 DB2 UDB V8.1 所提供的一些新增和改進的 SQLJ 特性。
SQLJ 的背景
1997 年 4 月,一個由數據庫供應商組成的非正式和開放小組開始定期開會,交流有關如何在 Java 編程語言中使用靜態 SQL 語句和構造的想法。主要參與者包括 IBM、Oracle、Compaq、Informix®、Sybase、Cloudscape 和 Sun Microsystems。該小組把他們正制定的規范命名為 JSQL。但他們發現術語 JSQL 是一個注冊商標,因此就將 JSQL 重命名為 SQLJ。1997 年 12 月,Oracle 向其它成員提供了一個 Java 中嵌入式 SQL 的參考實現。這個參考實現可以在任何支持 JDK 1.1 的平臺上運行,并且它與供應商無關。1998 年 12 月,完成了 Java 中嵌入式 SQL 規范的整個開發工作,并被接納為 ANSI 標準 Database Language - SQL, Part 10 Object Language Bindings (SQL/OLB)ANSI x3.135.10-1998。這個規范一般稱為 SQLJ 規范的第 0 部分。現在它被稱為 SQL/OLB(對象語言綁定,Object Language Binding)。
SQLJ 規范目前由兩部分組成:
- SQL/OLB:Java 中的嵌入式 SQL
這部分標準規定了在 Java 方法中嵌入 SQL 的語法和語義,還規定了一些機制來確保生成的 SQLJ 應用程序的二進制可移植性。這正是本文要闡述的主題。
- SQL/JRT:使用 Java 編程語言的 SQL 例程和類型
這部分標準包含以下內容:
- 將 Java 靜態方法作為 SQL 存儲過程和用戶定義的函數來調用的規范。它定義了 SQL 擴展,用于在 SQL 系統中安裝 Java 類,在 SQL 中以 SQL 函數和存儲過程方式調用 Java 類的靜態方法,獲取指定的參數輸出值以及返回 SQL 結果集。
- 將 Java 類用作 SQL 用戶定義的數據類型的規范。它定義了 SQL 擴展,用于將 Java 類用作 SQL 中的數據類型。
術語:當我們在本文其余部分使用術語 SQLJ 時,僅指 SQL/OLB。
SQLJ 編程環境
SQLJ 環境由兩個階段組成:開發和運行時。本節向您介紹每個階段所涉及到的組件以及各組件間的關系。
開發 SQLJ 應用程序
使用 SQLJ 開發應用程序需要三個組件:轉換程序、概要文件定制程序和概要文件綁定程序。有三個實用程序提供了支持這三個組件的功能,它們分別是: sqlj、db2sqljcustomize和 db2sqljbind。這里對 圖 1中演示的過程作一個概述:
- 首先,調用 SQLJ 轉換程序(sqlj)以讀取 SQLJ 源文件并檢查該程序中 SQLJ 語法的正確性。轉換程序生成一個 Java 源文件,可能不生成 SQLJ 概要文件或生成多個 SQLJ 概要文件,并且如果所生成的 Java 源文件中沒有錯誤,那么它還可以選擇將該源文件編譯成字節碼(缺省情況)。生成的 Java 源文件將嵌入式 SQL 替代為對執行 SQL 操作的 SQLJ 運行時的調用。
- 接著,調用 SQLJ 概要文件定制程序(db2sqljcustomize)來為生成的序列化概要文件創建 DB2 定制。該定制程序可以選擇(缺省情況下) 聯機檢查能夠動態編譯的 SQL 語句。聯機檢查執行語法、語義和模式驗證。也可以選擇(缺省情況下)調用 SQLJ 概要文件綁定程序以綁定 DB2 包。
- 如果選擇在概要文件定制期間不執行自動綁定,那么可以單獨調用 SQLJ 概要文件綁定程序(db2sqljbind),以將先前定制的 SQLJ 概要文件綁定到數據庫。
- 不管概要文件是否被定制,要查看其內容,可以使用 SQLJ 概要文件打印程序(db2sqljprint)以文本格式打印出概要文件的內容。
圖 1. SQLJ 開發環境
執行 SQLJ 應用程序
為訪問數據庫,SQLJ 運行時要依靠 JDBC 驅動程序來獲取數據庫連接。未定制的 SQLJ 應用程序可以與任何 JDBC 2.0 驅動程序一起運行。在開發期間,為了測試,只需運行未定制應用程序。要運行定制的 SQLJ 應用程序,可以使用 V8 基于 CLI 的 JDBC 類型 2 驅動程序 - 通用 JDBC 驅動程序(類型 2 或類型 4)來建立數據庫連接。本節中,我們只描述用于定制的 SQLJ 應用程序的運行時環境。
當您運行 SQLJ 應用程序時,SQLJ 運行時從定制的概要文件中讀取有關 SQL 操作的信息,并執行與存儲在定制中的包關鍵信息(包名、包一致性標記和集合名)相符的 DB2 包中的語句。
圖 2. SQLJ 運行時環境
SQLJ 相對于 JDBC 的優勢
SQLJ 規范和 JDBC 規范都描述了如何使用 Java 來訪問關系數據庫。本節從以下幾個方面討論它們之間的差異:
表 1匯總了我們在本節中所描述的 SQLJ 和 JDBC 之間的差異。
標準和 SQL 規范級別
SQLJ 是 ISO/IEC 9075-10:2000 Information technology -- Database languages -- SQL -- Part 10: Object Language Bindings (SQL/OLB)的實現。SQLJ 不屬于 J2EE 平臺。
JDBC 是 J2SE 1.4 和 J2EE 1.4 平臺規范的一個必不可少的組件。它自 Java 軟件開發工具箱(Java Software Development Kit,JDK)V1.1 之后已成為其核心部件。它包含在 java.sql 包中。JDBC 驅動程序必須至少支持 Entry SQL-92 語句,在該規范中還定義了一些擴展。
安全性
SQLJ 中實現的安全性權限模型是用戶考慮使用 SQLJ 的一個主要原因。使用靜態 SQL,安全性特權就被指派給了包創建者,并被存儲在 DB2 包中。
使用定制的 DB2 SQLJ,靜態地執行 SQL;因此使用包所有者的特權來執行 SQL 語句。任何運行 SQLJ 應用程序的其他用戶都必須被授予具有該包的 EXECUTE 特權。即,被授權可以運行程序的用戶未必有權對該程序所查詢或正在修改的同一表或視圖執行 SELECT、UPDATE、DELETE 或 INSERT 操作,除非顯式地授予該用戶相應的特權。
擁有連接到數據庫并執行 JDBC 應用程序特權的人可以執行這些應用程序中的 SQL 語句。因此,用戶必須獲得訪問表的特權。
性能
SQLJ 允許在 Java 程序中嵌入 SQL 語句,這類似于 SQL-92 允許 SQL 語句嵌入到 C、COBOL、FORTRAN 以及其它編程語言中的方式。但是,根據 SQLJ 概要文件是否被定制,可以決定 SQLJ 應用程序是動態還是靜態地運行。當將包存儲在 DB2 數據庫中時,就會預編譯 SQLJ 應用程序并優化 SQL 語句的路徑長度。靜態執行的 SQLJ 應用程序的性能會優于 JDBC 的性能。
如果想利用靜態執行(我們建議這樣做),必須使用 SQLJ 概要文件定制程序來定制概要文件。
JDBC 提供了 SQL 語句的動態執行。如果這些語句中存在語法或語義錯誤,那么在該應用程序運行時任何此類異常都會產生。
使用 DB2 UDB 監視器可以驗證靜態或動態的 SQL 語句處理。監控方法有兩種:快照監控和事件監控。快照監視器提供有關數據庫在某個特定時間點的活動信息。事件監視器記錄了 DB2 UDB 事件發生的特定位置。下面的 清單 1 摘自從 JDBC 程序生成的事件監視器的樣本輸出。“Type: Dynamic”告訴您動態執行了 SELECT job FROM staff WHERE name = ? 語句。
清單 1. 從 JDBC 程序生成的事件監視器的樣本輸出
10) Statement Event ...
Appl Handle: 23
Appl Id: G91AA377.G576.00F306261BF2
Appl Seq number: 0001
Record is the result of a flush: FALSE
-------------------------------------------
Type :
Dynamic
Operation: Prepare
Section : 1
Creator : NULLID
Package : SYSSH200
Consistency Token : SYSLVL01
Package Version ID :
Cursor : SQL_CURSH200C1
Cursor was blocking: FALSE
Text : SELECT job FROM staff WHERE name = ?
|
清單 2摘自從 SQLJ 程序生成的事件監視器的樣本輸出。輸出中的“Type: Static”和“Package: SRQT402”告訴您,對 SRQT402 包靜態執行了該語句。
清單 2. 從 SQLJ 程序生成的事件監視器的樣本輸出
10) Statement Event ...
Appl Handle: 12
Appl Id: G91ABD18.G47D.00F306C01D63
Appl Seq number: 0001
Record is the result of a flush: FALSE
-------------------------------------------
Type :
Static
Operation: Execute
Section : 1
Creator : NULLID
Package :
SRQT402
Consistency Token : SARoQCAp
Package Version ID :
Cursor :
Cursor was blocking: FALSE
|
注:有些語句對于定制的 SQLJ 程序會正確執行,但對于未定制的 SQLJ 程序就不會正確執行。可滾動游標的 UPDATE/DELETE WHERE CURRENT OF 就是這樣一個示例。一般而言,如果底層 JDBC 驅動程序不支持某個功能,那么未定制的 SQLJ 程序也不會支持該功能。
性能技巧: 對于單個 select 查詢,與盲目地對龐大的 JDBC ResultSets 執行操作相比,可以通過使用由 SQLJ 提供的 SELECT INTO 語法來減少網絡活動。
圖 3比較了用于單個 select 查詢的 SQLJ 和 JDBC 語法。
圖 3. 使用 SQLJ 和 JDBC 檢索一個行
SQLJ 語法:
#sql [conCtx] { SELECT job INTO :job FROM staff WHERE name = :name };
JDBC 語法:
PreparedStatement pstmt = con.prepareStatement(
"SELECT job FROM staff WHERE name = ? FETCH FIRST 1 ROW ONLY" );
ResultSet rs = pstmt.executeQuery();
if ( rs.next() )
job = rs.getString(1);
else
job = null;
pstmt.close();
|
|
語法
圖 3表明 SQLJ 語法在簡單性方面優于 JDBC。SQLJ 的簡單性受到了許多 Java 開發人員的歡迎。編寫 SQLJ 模塊通常要比 JDBC 模塊簡潔且容易。這暗示著 SQLJ 可以使開發周期縮短并減少開發和維護成本。 圖 4向您顯示了 SQLJ 可以多么簡單地向數據庫插入一行數據。如果您已有了用其它語言(如 C 或 COBOL)編寫的嵌入式 SQL 應用程序,那么就可以使用 SQLJ 輕松地將應用程序遷移到 Java。
圖 4. 使用 SQLJ vs. JDBC 插入一個行
SQLJ 語法:
sql [conCtx] { INSERT INTO sales VALUES(:date, :salesperson, :region, :sales) };
JDBC 語法:
PreparedStatement pstmt = con.prepareStatement( "INSERT INTO sales VALUES (?, ?, ?, ?)" );
// set input parameter
pstmt.setObject(1, date);
pstmt.setString(2, salesperson);
pstmt.setString(3, region);
pstmt.setInteger(4, sales);
pstmt.executeUpdate();
pstmt.close();
|
SQLJ 和 JDBC 互操作性
SQLJ 語言允許您在 SQLJ 應用程序中使用 JDBC 語句。要使 JDBC 和 SQLJ 之間便于交互,SQLJ 提供了一種方法以便在同一應用程序內共享 SQLJ 連接和 JDBC 連接,這種方法還可以從 SQLJ 迭代器中獲取 JDBC 結果集,或從 JDBC 迭代器中獲取 SQLJ 結果集。
何時需要在 SQLJ 應用程序中使用 JDBC? 您需要將 JDBC 用于動態操作時;即,在編寫程序時不清楚 SQL 操作的時候。 清單 3演示了在 SQLJ 程序內用 JDBC 來執行動態查詢(WHERE 子句中的名稱在開發時是未知的),以及如何將 JDBC 結果集轉換到 SQLJ 迭代器。
與 SQLJ 不同的是,JDBC 不能識別 SQLJ 語法,而且 SQL 語句不能嵌入到 JDBC 應用程序。
清單 3. 將 JDBC 結果集轉換到 SQLJ 迭代器
Public class ResultSetInterop
{
#sql public static iterator Employees (String name, double salary);
public static void main(String[] argv) throws SQLException
{
// the code for creating the SQLJ connection context (conCtx) and
// the Connection object (con) is omitted
// create a JDBC statement object to execute a dynamic query
Statement stmt = con.createStatement();
String query = "SELECT name, salary FROM staff WHERE ";
query += argv[0];
ResultSet rs = stmt.executeQuery(query);
Employees SalReport;
// turn a JDBC result set to an SQLJ interator using the CAST statement
#sql [conCtx] SalReport = {
CAST :rs };
while (SalReport.next()) {
System.out.println( SalReport.name() + " earns " + SalReport.salary() );
}
SalReport.close();
stmt.close();
}
}
|
|
類型和模式檢查
SQLJ 與 Java 類似的一點是,它也是強類型的。在將 SQLJ 源文件轉換成 Java 源文件時,SQLJ 轉換程序會檢查 SQLJ 語法。這類似于其它 DB2 預編譯器。而且,在轉換階段的 Java 編譯期間執行迭代器數據類型的轉換。例如,在禁止使用雙精度的迭代器列(如雇員工資)中,Java 編譯器就會阻止該列使用雙精度類型。因此, String hv = employees.salary(); 這樣的賦值在編譯時就會生成一個錯誤。另外,在概要文件定制期間也執行聯機檢查,以便可以較早地捕獲編程錯誤。
JDBC 不能在運行時之前進行語法或語義檢查。如果存在語法或語義錯誤,那么在應用程序運行時任何此類異常都會產生。
注:
- 在 V8.1 中,在概要文件定制期間執行聯機檢查,而在以前的發行版中這一操作是在轉換階段執行的。
- 有些 SQLJ 錯誤只有在運行時才被捕獲到。另外,不能動態編譯的語句不會進行聯機檢查。
差異匯總
表 1匯總了 SQLJ 和 JDBC 之間的差異。
表 1. 比較 SQLJ 和 JDBC
|
SQLJ |
JDBC |
標準 |
ISO/ANSI(不屬于 J2EE) |
Sun(屬于 J2EE) |
SQL 規范級別 |
SQL-1999 |
N/A(必須至少支持 Entry Level SQL-92) |
安全性 |
強 |
一般 |
性能 |
較快(在開發期間創建了靜態存取方案) |
較慢(在應用程序執行期間創建了動態存取方案) |
語法 |
高級(緊湊) |
低級(繁瑣) |
SQLJ 和 JDBC 互操作性 |
是 |
N/A |
類型和模式檢查 |
強(在開發期間執行) |
弱(在運行時期間執行) |
V8.1 中的新增功能
DB2 UDB V8.1 提供了新設計的 SQLJ 驅動程序,它有幾個新特性。新的 SQLJ 驅動程序基于一種稱為 Distributed Relational Database Architecture?(DRDA®)的開放分布式協議。基于 CLI 的 JDBC 驅動程序(類型 2 和類型 3)以及 V8.1 中引入的新的通用 JDBC 驅動程序(類型 2 和類型 4)都支持該協議。
SQLJ 的主要增強功能可以概括如下:
新的 SQLJ 實用程序和運行時
DB2 UDB V8.1 中的 SQLJ 實現了純 Java SQLJ 實用程序和運行時,并帶有一些新選項和可選的格式。新的運行時性能比 V7 的性能好得多。
在 V8.1 中,SQLJ 轉換程序 sqlj 在缺省情況下總是編譯所生成的 Java 源文件。在 V7 中,這個編譯選項不能與某些 JDK 一起使用,因此您必須手工編譯 Java 文件。
V8.1 中新的概要文件打印程序 db2sqljprint 不再需要您提供 URL,而且它提供了有關要執行的 SQL 語句的詳細信息,例如 DB2 語句的類型、節號以及 DB2 結果集元數據信息。
消除了特定于平臺的文件
V8.1 中的 SQLJ 概要文件定制程序包含新的序列化概要文件格式,它不必使用 DBRM 文件和綁定文件(.bnd 文件)。新格式完全可以移植到所有平臺。它包含所有 BIND 操作所需的所有信息,用戶不必在目標系統(UNIX®、Windows®、OS/390® 和 z/OS?)上重新定制序列化概要文件就可以部署在任何服務器平臺上。
V8 中的新特性
DB2 UDB V8.1 中添加的主要特性包括以下各項:
使用 DataSource 創建 SQLJ 連接上下文
有了 V8.1 SQLJ,您可以使用 JDBC DataSource 接口來創建 SQLJ 連接。而且,缺省連接上下文的實現也更改了。要獲得缺省連接上下文,SQLJ 運行時要執行 JNDI 查詢以獲得 jdbc/defaultDataSource。如果沒有注冊任何 jdbc/defaultDataSource,那么在驅動程序試圖訪問上下文時,就會拋出一個空上下文異常。因此,必須向 JNDI 注冊 jdbc/defaultDataSource,或者通過調用 DefaultContext.setDefaultContext(userctxt) 來設置缺省上下文。
建議:在 SQLJ 子句中使用顯式的連接上下文。
清單 4. 使用數據源創建 SQLJ 連接上下文
// Create connection context class Ctx with the new dataSource keyword
#sql public static context Ctx with (dataSource="jdbc/sampledb");
String userid, password;
String empname;
?
// Create connection context object conCtx for the connection to jdbc/sampledb
Ctx conCtx = new Ctx(userid, password);
#sql [conCtx] { SELECT lastname INTO :empname FROM emp WHERE empno = '000010' };
?
conCtx.close();
|
|
可滾動的迭代器
可滾動的迭代器允許您向前移、向后移或移動到指定行。與 JDBC 中可滾動的游標相似,可滾動的迭代器可以是 不敏感的,也可以是 敏感的。
- 不敏感的迭代器意味著在迭代器打開后,它不能看到底層表中的更改。不敏感的迭代器是只讀的。
- 敏感的迭代器意味著迭代器可以看到迭代器或其它處理對底層表所作的更改。例如, 清單 5中的代碼演示了如何使用指定的迭代器對雇員表的所有行以逆序方式檢索雇員號和雇員的姓氏。
清單 5. 使用可滾動的迭代器
// Declare a scrollable iterator.
#sql iterator ScrollIter implements sqlj.runtime.Scrollable with (sensitivity = SENSITIVE)
(String EmpNo, String LastName);
{
ScrollIter scrlIter;
#sql [conCtx] scrlIter={ SELECT empno, lastname FROM emp };
scrlIter.afterLast();
while (scrlIter.previous())
{
System.out.println(scrlIter.EmpNo() + " " + scrlIter.LastName());
}
scrlIter.close();
}
|
|
批處理更新
批處理更新允許將語句集中到一起,隨后將其以批處理方式發送到數據庫,一次執行完這些語句。您可以在批處理更新中包含以下幾種類型的語句:
- 搜索到的 INSERT、UPDATE 或 DELETE 語句
- CREATE、ALTER、DROP、GRANT 或 REVOKE 語句
- 只帶輸入參數的 CALL 語句
與 JDBC 不同,SQLJ 允許異構批處理,這些批處理中包含帶有輸入參數或主機表達式的語句。因此在同一個 SQLJ 語句批處理中可以包含同一語句的實例、不同語句的實例、帶輸入參數或主機表達式的語句的實例以及不帶輸入參數或主機表達式的語句的實例等。
建議:在關閉批處理或在結束使用 ExecutionContext(會使批處理打開)之前,要顯式地調用 executeBatch()。這將確保執行經過批處理的所有語句。
清單 6中的代碼段向您顯示了如何以批處理方式執行更新操作來給所有管理人員加薪。
清單 6. 執行批處理更新
#sql iterator getMgr(String);
{
getMgr deptIter;
String mgrnum = null;
int raise = 400;
int currentSalary;
String url = null, username = null, password = null;
testContext conCtx = new testContext (url, username, password, false);
// Acquire execution context.
// All statements that execute in a batch must use this execution context.
ExecutionContext exeCtx = new ExecutionContext();
// Invoke ExecutionContext.setBatching (true) to create a batch.
exeCtx.setBatching(true);
#sql [conCtx] deptIter = { SELECT mgrno FROM dept };
#sql {FETCH :deptIter INTO :mgrnum};
while (!deptIter.endFetch())
{
#sql [conCtx] {
SELECT SALARY INTO :currentSalary FROM emp WHERE empno = :mgrnum};
#sql [conCtx, exeCtx]
{ UPDATE emp SET SALARY = :(currentSalary+raise) WHERE empno =:mgrnum };
#sql { FETCH :deptIter INTO :mgrnum };
}
exeCtx.executeBatch();
exeCtx.setBatching(false);
#sql [conCtx] {COMMIT};
deptIter.close();
exeCtx.close();
conCtx.close();
}
|
結束語
相對于 JDBC,SQLJ 的 DB2 實現具有明顯的優勢。在預編譯時 SQLJ 較簡單語法和類型以及模式檢查大大降低了開發成本。SQLJ 還具有在 SQLJ 應用程序中嵌入 JDBC 語句這樣的靈活性。這意味著一個應用程序可以同時利用 SQLJ 和 JDBC 的優點。當安全性和性能對 Java 應用程序至關重要時,SQLJ 是正確的選擇。
更多信息
關于作者
 |

|
 |
Connie Tsui是 IBM 多倫多實驗室 DB2 解決方案集成團隊的專職軟件分析師。她從多倫多大學(University of Toronto)獲得了計算機科學學士學位。她目前主要從事 DB2 和 WebSphere® 的集成。
|
|