簡介
我該學習本教程嗎?
本教程提供了技巧和技術,以幫助您最優化用 Java 應用程序訪問 DB2 V8.1,並且最優化對新的 IBM DB2Universal Driver for SQLJ and JDBC 1.0 的使用。本文還描述了如何避免可能影響這些應用程序性能的常見錯誤。
您應該熟悉 Java 編程和 DB2 的基礎知識以及相關概念。熟悉 SQL 和 JDBC 會有幫助,但並非必需。
本教程是關於什麼的?
Java 語言提供了兩種用來訪問關系數據庫中數據的接口:低級應用程序編程接口 JDBC 和允許您在 Java 程序中直接嵌入 SQL 語句的 SQLJ。
JDBC 和 SQLJ 都可以用來訪問各種關系數據庫(包括 DB2 和 Oracle)中的數據。本教程主要討論在使用 SQLJ 訪問 DB2 時如何獲得最佳性能。
所涵蓋的主題包括:
SQLJ 和 JDBC
SQLJ 基礎知識
靜態 SQL 和 SQLJ
通過 Java 應用程序優化 DB2 性能
避免 SQLJ 和 JDBC 中的常見錯誤
安全性、SQLJ 和 JDBC
同一程序中的 SQLJ 和 JDBC
工具
代碼樣本用來說明本教程中所討論的技巧和技術。要運行本教程中的一些樣本,請在開始之前安裝和測試下列工具:
符合 JDBC 規范的數據庫,如 DB2 通用數據庫 V8.1。可以免費下載 IBM DB2 通用數據庫 V8.1 試用版。DB2 包含對 SQLJ 的支持。
Java 2 SDK,標准版,V1.3.1 或更新版本。
概述
Java 語言和數據訪問的發展
在過去的五年中,Java 語言得到了越來越廣泛的使用。自從 1992 年誕生以來,它迅速地成為了世界上應用最為廣泛的編程語言之一。據估計,目前 Java 程序員的人數超過了 250 萬。
Java 語言有兩個主要特征:
它是一種面向對象的語言。Java 語言使用類,類是由實現類功能的方法和保存類數據的字段組成。Java 類充當對象的模板,對象則是作為類的實例而創建的。類可以是另一個類的子類,從父類繼承字段和方法。Java 語言還有接口,它們是對特定類中所用方法的描述。通常用類來實現接口中的方法。最後,您可以通過在程序中包含庫來將接口和類添加到 Java 程序。
它是跨平台的。Java 語言可以在當前流行的大多數操作系統上運行。盡管不同平台上的 Java 實現可能有細微的差別,但 Java 語言的核心在任何平台上都是相同的。這種共性意味著您可以在任何受支持的平台上進行開發,而不必再經歷陡峭的學習曲線,或為了在某種平台上運行而修改代碼。
Java 語言包括許多不同的接口和類,用來向 Java 程序提供功能。
功能的主要方面之一就是訪問要在 Java 程序中使用的數據。在 Java 語言中,有兩種用於數據訪問的開放標准方法:JDBC 和 SQLJ。
JDBC
JDBC 提供了通過調用級別接口從關系數據庫中訪問數據的基本功能。使用 JDBC 接口,您可以編寫用動態 SQL 語句從 DB2 中檢索數據的 Java 程序。使用 JDBC 時,您不必預先定義將要用來訪問數據的 SQL。
當用 JDBC 從 DB2 數據庫中的表中選擇數據時,下列代碼是必需的:
Java.sql.PreparedStatement prepStmt =
context.prepareStatement("SELECT EMPNO FROM EMP_ACT WHERE PROJNO =?");
prepStmt.setString(1, strName);
Java.sql.ResultSet rs = prepStmt.executeQuery;
//RetrIEve data
rs.close();
SQLJ
SQLJ 構建在 JDBC 之上,使用嵌入式 SQL 來訪問數據庫。使用 SQLJ 只需要將 SQL 語句嵌入到 Java 代碼中 ― 而不必處理底層的調用級別接口。
要用 SQLJ 執行(上一頁所描述的)SELECT 語句,請使用以下代碼:
#sql [context] {SELECT EMPNO INTO :strEmpNo FROM EMP_ACT WHERE PROJNO =:strProjNo};
除了減少一些編碼需求之外,使用 JDBC 和使用 SQLJ 訪問 DB2 的主要差異就在於 JDBC 總是使用動態 SQL。SQLJ 可以使用動態 SQL,但也可以通過定制過程,使之能夠使用靜態 SQL 語句,靜態 SQL 一章將討論這一主題。
SQLJ 和 JDBC 之間的另一個重要差異是對預編譯步驟的使用。必須通過預編譯器運行帶 SQLJ 語句的 Java 程序,以將 SQLJ 偽指令轉換成 Java 代碼。
SQLJ 和 JDBC
本教程著重討論使用 SQLJ 來最優化 DB2 性能。我們為什麼使用 SQLJ 而不用 JDBC 呢?
有以下幾個使用 SQLJ 的理由:
完成同樣的數據訪問任務,SQLJ 所需要的代碼行往往更少。更少的代碼行數意味著在開發應用程序上花費的時間更少,調試和維護應用程序所花費的時間也更少。
使用 SQLJ,可以使用定制過程來檢查程序內 SQL 語句的語法。這個過程消除了發生運行時錯誤的可能性。
SQLJ 用游標和語句實現 SQL,這和其它編程語言中的 SQL 很相似。如果您對用其它語言創建應用程序很熟悉,但在 Java 編程方面是個新手,那麼使用 SQLJ 有助於降低您的整體學習曲線。
JDBC 可以做一件 SQLJ 不能做的事情 ― 執行動態 SQL 語句。對於需要使用一些動態 SQL 的應用程序,您總是可以在程序中包含 JDBC 代碼的同時包含一些 SQLJ 代碼。有關在應用程序中包含這兩者的信息,以及一些使用 JDBC 的技巧,請參閱同一應用程序中的 SQLJ 和 JDBC。
下一章討論了使用 SQLJ 的基本語法。
SQLJ 基礎知識
准備
為了在 Java 程序中使用 SQLJ 來訪問 DB2,您需要在開始編碼之前采取一些步驟。在應用程序的目錄或 CLASSPATH 中包括下列文件。
db2jcc.jar,它提供 JDBC 驅動程序(類型 4)
sqlj.zip ,它提供 SQLJ 轉換程序類文件,在 SQLJ 中將有所討論
這些文件都位於 SQLLIB/Java 目錄。
在代碼中,可以用下列 import 語句來包括所需的類和接口:
import Java.sql.*;
import sqlj.runtime.*;
import sqlj.runtime.ref.*;
基本 SQLJ 語法
SQLJ 將 SQL 用作訪問和操作數據庫中數據的方法。為了使用這些嵌入在 Java 程序中的 SQL 語句,請使用 SQLJ 預編譯器可以識別的語法。
任何嵌入式 SQLJ 語句都必須遵守兩個簡單規則:
語句必須由語法 #sql 開頭。
語句必須由分號(;)結尾。
您還應該將 SQLJ 語句放在大括號中,並且還要寫上執行該語句的上下文(盡管這是可選的)。下列代碼段說明了這些需求和建議的使用:
#sql [context] {DELETE FROM EMP_ACT};
注:本教程中使用的所有表都來自於 DB2 SAMPLE 數據庫。
從應用程序傳遞信息
前一頁中使用的簡單 SQL 語句不需要從發出該語句的 Java 應用程序向該語句傳遞任何信息。但某些類型的 SQL 語句(如 INSERT)需要從 Java 程序向嵌入式 SQL 語句傳遞數據。
應用程序可以通過使用主機變量來傳遞這種數據。主機變量只是一個變量,它是執行調用的 Java 程序的一部分,由一個冒號(:)開頭,以表明其來源。
例如,要對 EMP_ACT 表進行插入操作,請使用下列語法:
void m (String empno, String projno, int actno) throwsSQLException
{
#sql [context]{INSERT INTO EMP_ACT (EMPNO, PROJNO, ACTNO)
values (:empno, :projno,:actno)};
}
完整的應用程序
既然您已經理解了 SQLJ 的基本知識,那麼可以將它全部用於完整的應用程序。正如本教程先前部分所描述的,這個應用程序的步驟包括創建 URL 和連接以訪問的數據庫,以及使用 SQLJ。
此外,這個示例還包括異常處理和用於注冊 DB2 JDBC 驅動程序的代碼。
import Java.sql.Connection;
import Java.sql.DriverManager;
import sqlj.runtime.ref.DefaultContext;
public class SqlJDemo
{
static
{
try
{
//register the DB2 JDBC driver with DriverManager
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 {
//Commit any remaining transactions
#sql [ctx] {commit};
ctx.close();
con.close();
} catch(Java.sql.SQLException sqle) {
sqle.printStackTrace();
}
}
public void makeConnection()
{
try
{
// get context and open connection to DB2
ctx = DefaultContext.getDefaultContext();
if (ctx == null)
{
//construct the URL (sample is the database name)
String url = "jdbc:db2://localhost:50000/SAMPLE";
//connect to the 'sample' database with user ID and passWord
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 = null;
String projno = null;
int actno;
// Set values for host variables
empno = "01";
projno = "10";
actno = 1;
// insert data into EMP_ACT
#sql [ctx] {INSERT INTO EMP_ACT (EMPNO, PROJNO, ACTNO)
values (:empno, :projno, :actno)};
// commit inserted record
#sql [ctx] {commit};
} catch(Exception e) {
e.printStackTrace();
}
}
}
檢索多行數據
到目前為止,所有示例使用的 SQL 語句都不返回數據。要從 DB2 返回一行或多行數據,使用 SELECT 語句。SELECT 語句返回多個行,它們是 SQL 查詢的結果。檢索和使用這個結果集中的行需要一些額外的 SQLJ 語法。
因為結果集可以包括多行數據,所以需要聲明一個迭代器來遍歷這些行。而且必須在任何方法以外聲明迭代器,因為當對它們執行 SQLJ 轉換程序時,會將它們轉換成單獨的類。然後,您可以創建一個迭代器實例並填充該實例。
您可以使用兩種不同類型的迭代器 ― 指定名稱迭代器或位置型迭代器。對於指定名稱迭代器,用列名及其數據類型聲明迭代器,其語法如下所示:
#sql iterator NamedIterator (String empno, String projno, int actno);
要檢索數據,請創建一個迭代器類實例並通過賦給它 SQL 語句來填充它。然後,您可以通過使用 while 循環來循環遍歷該數據,用 NamedIterator 類中創建的方法檢索每個列:
...
}
}
static Connection con;
static DefaultContext ctx;
#sql iterator NamedIterator (String empno, String projno, int actno );
public static void main (String args[]){
SqlJDemo demoApp = new SqlJDemo();
demoApp.makeConnection();
demoApp.insertData();
demoApp.useNamedIterator();
}
public void useNamedIterator()
{
try
{
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();
} catch (Exception e) {
e.printStackTrace();
}
}
public void makeConnection()
{
try
{
...
位置型迭代器
對於位置型迭代器,聲明迭代器時只需指定數據類型,其語法如下所示:
#sql iterator PosIterator (String, String, int);
您還需要聲明主機變量來檢索數據,並在程序代碼中創建迭代器的實例。使用 FETCH ... INTO 語法來將列檢索到主機變量中,其語法如下所示:
...
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();
demoApp.useNamedIterator();
demoApp.usePositionalIterator();
}
public void usePositionalIterator()
{
try
{
String empno = null;;
String 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())
{
// row logic
System.out.println(empno+" "+projno+" "+actno);
#sql {FETCH :posIter INTO :empno, :projno, :actno};
}
posIter.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public void useNamedIterator()
{
try
...
編譯 SQLJ
在本教程前面的部分中,頁面 SQLJ 提到過必須通過預編譯器來運行包含 SQLJ 偽指令的 Java 代碼。這個預編譯器將 SQLJ 偽指令轉換成 Java 代碼。
要完成這個轉換,使用 sqlj 轉換程序。例如,這個示例中的應用程序名為 SqlJDemo.sqlj,因此轉換它涉及下列命令:
sqlj SqlJDemo.sqlj
這個示例展示了使用 sqlj 轉換程序的最簡單形式,它對如 DB2 驅動器路徑這樣的參數都采用缺省值。
sqlj 轉換程序創建一個名為 SqlJDemo.java 的文件,該文件包含從 SQLJ 偽指令生成的 Java 代碼。
轉換程序還為應用程序中的每個連接上下文類創建概要文件。對於這個應用程序,該概要文件的名稱為 SqlJDemo_SJProfile0.ser。如果這個應用程序有多個上下文類,那麼它們的名稱將是 SqlJDemo_SJProfile1.ser、SqlJDemo_SJProfile2.ser、SqlJDemo_SJProfile3.ser 等。
准備該 Java 程序的下一步是為程序中的靜態 SQL 在 DB2 中創建包。本教程的下一章將討論靜態 SQL。
靜態 SQL
什麼是靜態 SQL?
要理解靜態 SQL,就必須首先理解 DB2 是如何使用靜態 SQL 語句的。
SQL 是一種標准訪問語言,但每種數據庫都必須將這種標准語法修改為可以有效使用的調用。這種修改有三個基本步驟:
對 SQL 語法進行檢查,以找出語法錯誤,並確保語句中的數據庫對象存在並具有正確的數據類型。
對 SQL 語法進行最優化。DB2 通過這個過程來確定讀或寫語句中所表示的數據的最佳訪問路徑。對於簡單數據庫表,這種最優化過程的作用也許很有限,但如果對於一個需要對多個表進行操作的復雜的 SQL 語句,而這些表又帶有很多索引,那麼最優化過程可能必須考慮許多選項來獲得最佳路徑。
執行 SQL 語句。
對於動態 SQL,盡管 DB2 可以為重復的動態 SQL 高速緩存前兩個步驟的結果,但還是必須對每個 SQL 語句重復上面的所有步驟。
對於靜態 SQL,預編譯 SQLJ 應用程序時會創建關於靜態 SQL 語句的信息。然後您可以創建包(存儲在 DB2 中),它是上述過程中前兩個步驟的結果。當與包匹配的程序運行時,只是將 SQL 語句聯編到這些結果,這樣可以帶來更低的開銷和更佳的性能。
從 Java 代碼創建包
要完成使用 Java Common Connectivity(JCC)驅動程序的 SQLJ 應用程序的准備工作,您必須對預編譯過程期間生成的概要文件進行定制。定制將創建隨後可以聯編到數據庫的包。
要為 SQLJ 應用程序創建包,對前一章(SQLJ)中描述的、作為預編譯過程一部分創建的每一個服務器概要文件使用 db2sqljcustomize 命令。如果不對 SQLJ 應用程序使用這條命令,則應用程序將動態執行程序中的 SQL 語句。
db2sqljcustomize 命令的語法如下:
db2sqljcustomize -user username -password passWord -url url
profilename
其中,username 和 passWord 是在連接到數據庫時使用的用戶名和該用戶名的密碼,url 是數據庫的 URL。您還可以使用 onlinecheck 開關檢查大多數嵌入在應用程序中的 SQL 語句。
例如,為樣本應用程序定制的概要文件使用了下列命令:
db2sqljcustomize -user myusername -password mypassWord
-url jdbc:db2://localhost:50000/sample -onlinecheck YES SqlJDemo_SJProfile0.ser
profilename 是作為 SQLJ 編譯過程一部分而生成的概要文件的名稱。請記住,應用程序中所使用的每個連接上下文都有一個概要文件。
缺省情況下,定制自動將所生成的包聯編到數據庫,但也可以單獨地使用 db2sqljbind 來做到這一點,如下所示:
db2sqljbind -user myusername -password mypassWord
-url jdbc:db2://localhost:50000/sample SqlJDemo_SJProfile0.ser
您可以用 db2sqljprint 打印出 DB2 概要文件的可讀純文本版本。這個命令使用如下語法:
db2sqljprint profilename
最優化 DB2 性能
最優化性能概述
前一章討論了靜態 SQL 的使用,靜態 SQL 在許多情況下能夠比 JDBC 所用的動態 SQL 運行得更快。本章將討論許多其它技術,您可以在使用 SQLJ 或 JDBC 時用它們來最優化性能。
禁用 AutoCommit
在本教程第一部分所示的示例中,您建立了到 DB2 數據庫的連接。如果您使用 JDBC 建立連接,那麼缺省情況下,對於數據庫,AutoCommit 特性是打開的。
AutoCommit,顧名思義就是對提交到數據庫的每個 SQL 語句,都進行自動提交。這種自動功能可以減少您必須進行的 SQL 編碼工作量,但它同時會使 DB2 的整體響應時間變慢,因為每個語句在執行時都需要額外的開銷。
AutoCommit 特性還可能會影響應用程序的整體完整性,因為您最終有可能無法回滾應用程序所進行一系列更改。
要關閉連接的 AutoCommit,使用下列代碼來創建連接:
con = DriverManager.getConnection(url, userid,passWord);
con.setAutoCommit(false);
限制列
SQL 是一種簡明而又功能強大的語言。只用幾個簡單的單詞,譬如:
SELECT * FROM EMP_ACT
就可以從表中檢索所有數據。
但僅因為可以這樣做並不意味著應該這樣做!因此雖然上述語句很容易編寫,但對於 DB2,它采用如下形式: