本教學提供技巧與技術,協助您最佳化存取 DB2 V8.1 的 Java 應用程式,並充分發揮 IBM DB2 Universal Driver for SQLJ and JDBC 1.0 的效能。文中也說明如何避免影響應用程式效能的常見錯誤。讀者需具備 Java 程式設計與 DB2 基礎知識,熟悉 SQL 與 JDBC 將有助於理解,但非必要。
Java 語言具備物件導向與跨平台兩大特性。在資料存取方面,Java 提供兩種開放標準介面:JDBC(低層級呼叫介面)與 SQLJ(嵌入式 SQL)。
JDBC 透過動態 SQL 存取關聯式資料庫。範例:
java.sql.PreparedStatement prepStmt =
context.prepareStatement("SELECT EMPNO FROM EMP_ACT WHERE PROJNO =?");
prepStmt.setString(1, strName);
java.sql.ResultSet rs = prepStmt.executeQuery();
// 擷取資料
rs.close();
SQLJ 建構於 JDBC 之上,允許在 Java 程式碼中直接嵌入 SQL 陳述式,減少底層 API 呼叫。同樣查詢的 SQLJ 寫法:
#sql [context] {SELECT EMPNO INTO :strEmpNo FROM EMP_ACT WHERE PROJNO =:strProjNo};
主要差異:JDBC 僅支援動態 SQL;SQLJ 可透過定製化過程使用靜態 SQL,大幅降低執行期開銷。此外,SQLJ 需經預編譯器將偽指令轉換為 Java 代碼。
JDBC 唯一優勢在於執行動態 SQL,但可在同一程式中混用 JDBC 與 SQLJ(詳見後章)。
在應用程式目錄或 CLASSPATH 中包含:
db2jcc.jar (JDBC Type 4 驅動程式)sqlj.zip (SQLJ 轉換器類別檔)兩者均位於 SQLLIB/java 目錄。代碼中匯入:
import java.sql.*;
import sqlj.runtime.*;
import sqlj.runtime.ref.*;
#sql 開頭。; 結尾。#sql [context] {DELETE FROM EMP_ACT};
使用主機變數(以冒號 : 開頭)從 Java 應用程式傳遞資料至 SQL 陳述式:
void m (String empno, String projno, int actno) throws SQLException {
#sql [context] {INSERT INTO EMP_ACT (EMPNO, PROJNO, ACTNO) VALUES (:empno, :projno, :actno)};
}
包含註冊驅動程式、建立連線、插入資料、異常處理與關閉連線:
import java.sql.Connection;
import java.sql.DriverManager;
import sqlj.runtime.ref.DefaultContext;
public class SqlJDemo {
static {
try { Class.forName("com.ibm.db2.jcc.DB2Driver").newInstance(); }
catch (Exception e) { e.printStackTrace(); }
}
static Connection con;
static DefaultContext ctx;
#sql iterator NamedIterator (String empno, String projno, int actno);
#sql iterator PosIterator (String, String, int);
public static void main (String args[]) {
SqlJDemo demoApp = new SqlJDemo();
demoApp.makeConnection();
demoApp.insertData();
try {
#sql [ctx] {commit};
ctx.close(); con.close();
} catch (java.sql.SQLException sqle) { sqle.printStackTrace(); }
}
public void makeConnection() {
try {
ctx = DefaultContext.getDefaultContext();
if (ctx == null) {
String url = "jdbc:db2://localhost:50000/SAMPLE";
con = DriverManager.getConnection(url, "myusername", "mypassword");
con.setAutoCommit(false);
ctx = new DefaultContext(con);
DefaultContext.setDefaultContext(ctx);
}
} catch (Exception e) { e.printStackTrace(); }
}
public void insertData() {
try {
String empno = "01", projno = "10"; int actno = 1;
#sql [ctx] {INSERT INTO EMP_ACT (EMPNO, PROJNO, ACTNO) VALUES (:empno, :projno, :actno)};
#sql [ctx] {commit};
} catch (Exception e) { e.printStackTrace(); }
}
}
SELECT 陳述式返回結果集,需宣告迭代器遍歷。迭代器必須在方法外宣告(預編譯時會轉換為獨立類別)。
宣告時指定欄位名稱與型別:
#sql iterator NamedIterator (String empno, String projno, int actno);
使用方式:
NamedIterator namedIter = null;
#sql [ctx] namedIter = {SELECT EMPNO, PROJNO, ACTNO FROM EMP_ACT};
while (namedIter.next()) {
System.out.println(namedIter.empno() + " " + namedIter.projno() + " " + namedIter.actno());
}
namedIter.close();
宣告時僅指定型別順序:
#sql iterator PosIterator (String, String, int);
需宣告主機變數接收資料,使用 FETCH ... INTO 語法:
String empno = null, projno = null; int actno = -1;
PosIterator posIter = null;
#sql [ctx] posIter = {SELECT EMPNO, PROJNO, ACTNO FROM EMP_ACT};
#sql {FETCH :posIter INTO :empno, :projno, :actno};
while (!posIter.endFetch()) {
System.out.println(empno + " " + projno + " " + actno);
#sql {FETCH :posIter INTO :empno, :projno, :actno};
}
posIter.close();
使用 sqlj 轉換器將 .sqlj 檔轉換為 .java 檔:
sqlj SqlJDemo.sqlj
轉換器產生 SqlJDemo.java 及每個連接上下文的輪廓檔 SqlJDemo_SJProfile0.ser。下一步需對輪廓檔進行定製化,在 DB2 中建立靜態 SQL 包。
DB2 執行 SQL 三步驟:語法檢查、最佳化(決定存取路徑)、執行。動態 SQL 每次執行皆重複三步驟;靜態 SQL 在預編譯時完成前兩步驟,結果儲存為包,執行時直接綁定,開銷更低、效能更佳。
使用 db2sqljcustomize 對輪廓檔定製化,自動將生成的包綁定至資料庫:
db2sqljcustomize -user myusername -password mypassword -url jdbc:db2://localhost:50000/sample -onlinecheck YES SqlJDemo_SJProfile0.ser
亦可單獨使用 db2sqljbind 綁定,或用 db2sqljprint 列印可讀版輪廓檔。
JDBC 連線預設開啟 AutoCommit,每個 SQL 陳述式自動提交,雖減少編碼但增加開銷,並可能影響交易完整性。建議關閉:
con = DriverManager.getConnection(url, userid, password);
con.setAutoCommit(false);
避免使用 SELECT *,僅選取所需欄位,減少網路傳輸與 I/O 開銷。
對重複執行的動態 SQL,使用參數標記 (?) 避免重複編譯,重複利用存取計劃。
大量 INSERT/UPDATE/DELETE 時,使用 PreparedStatement.addBatch() 與 executeBatch() 減少網路往返。
根據應用程式需求設定適當的 setFetchSize(),平衡記憶體使用與網路往返次數。
Web 環境中務必使用連線池(如 WebSphere DataSource),避免頻繁建立/關閉連線的高昂成本。
使用參數標記防止 SQL 注入;限制資料庫帳號權限;加密敏感資料傳輸(SSL)。
需動態 SQL 時可在 SQLJ 程式中穿插 JDBC 代碼。注意共用同一連線上下文時的交易邊界管理。
IBM 提供 Developer Workbench、DB2 Control Center 等工具協助開發、除錯與效能調優。