使用SQLJ進行數據庫開發 第二部分:SQLJ語言元素
在我的第一篇文章中,我描述了什麼是SQLJ,把它同PL/SQL和JDBC進行了比較,並分析了從SQLJ獲得的好處。在這篇文章中,我闡述了SQLJ編程語言的基本原理,這樣你能做好準備,以使用真正的SQLJ。
SQLJ程序是嵌入了SQL語句的標準Java程序,這些SQL語句從#sql標記開始,到分號結束。有兩種SQLJ語句:聲明和可執行語句。
聲明語句宣佈連接環境和迭代器。連接環境被用於建立數據庫連接,而迭代器被用於保存SQL查詢的結果集。這二者中,後者被用得更多。可執行語句執行嵌入的SQL語句和PL/SQL塊。因爲SQLJ程序要被翻譯和通過JDBC運行,所以任何被JDBC驅動程序支持的SQLJ語句(參加下面內容)都可以被嵌入進SQLJ可執行語句。可執行語句也可以包含主機表達式,通過Java變量,它被用來在Java程序和數據庫之間交換信息。
Oracle JDBC驅動程序
Oracle提供下面的JDBC驅動程序:
客戶端廋驅動程序是一個100%的Java驅動程序,由沒有安裝Oracle的客戶端使用,Oracle推薦通過applets使用它。當Java applet運行時,瀏覽器能夠下載這些驅動程序。
OCI驅動程序(OCI8和OCI7)通過Oracle客戶安裝,客戶端使用這些驅動程序。Oracle JDBC OCI驅動程序通過從Java直接調用Oracle調用接口(OCI)訪問數據庫,提供Oracle 7、8和8i版本之間最高的兼容性。這些驅動程序需要通過Net8的Oracle客戶安裝。
服務器端廋驅動程序提供同客戶端廋驅動程序相同的功能,但是它運行在Oracle數據庫內部,並且可以訪問遠程數據庫。作爲一箇中間件,從一個Oracle服務器訪問一個遠程的Oracle服務器,或者更一般地,從內部其它的Oracle服務器,如從任何Java存儲過程或EJB訪問一個Oracle服務器時,這種驅動程序很有用。
服務器端內部驅動程序,叫做KPRB(核心程序包),支持運行在完成SQL操作的目標Oracle數據庫內部的任何Java代碼。服務器端內部驅動程序允許JServer JVM直接同SQL引擎通訊,在Oracle 8i/9i服務器上,它是作爲存儲過程、存儲函數、中間件、EJB或者CORBA對象運行的Java代碼的默認JDBC驅動程序。
由於運行在Oracle JServer內部,同時被專門優化,KPRB JDBC驅動程序非常靈活和有效率。你在寫SQLJ存儲過程時,將會用到它。
接下來,讓我們分析描述的SQLJ元素:連接環境、迭代器、可執行語句和主機表達式。
連接環境
要建立一個簡單的連接,在構造DefaultContext對象的時候,可以使用一個DefaultContext的實例,指定數據庫的URL、用戶名和密碼來實現,而最容易的方法是使用由Oracle公司提供的oracle.sqlj運行時的connect()方法。在下面的例子中,我們將使用JDBC廋驅動程序,通過1521端口把用戶“scott”連接到服務器MYSERVER的數據庫,密碼是“tiger”,ORCL是被連接的數據庫的SID:
Oracle.connect("jdbc:oracle:thin@MYSERVER:1521:ORCL", "scott", "tiger");
這個例子創建DefaultContext類的一個實例,並且把它安裝爲你默認的連接。有了這個DefaultContext的實例,不再需要其他的任何東西。
要建立多個連接,可以通過Oracle.getConnection()方法,創建和使用DefaultContext類另外的實例。在在下面的例子中,我們將使用Oracle OCI8驅動程序,把MYSERVER_ORCL作爲一個Oracle服務器的名字,在TNSNames.ora中創建一個ORCL實例:
DefaultContext myContext1 = Oracle.getConnection
("jdbc:oracle:oci8@MYSERVER_ORCL", "scott", "tiger");
DefaultContext myContext2 = Oracle.getConnection
("jdbc:oracle:oci8@MYSERVER_ORCL ", "tom", "bear");
這段代碼創建兩個連接環境實例,他們都使用相同的Oracle OCI8驅動程序,但是使用不同的schemas。通過爲每一個語句指定連接,在這兩個schemas中都可以完成SQL操作:
#sql [myContext1] { SQL statement };
...
#sql [myContext2] { SQL statement };
在程序的最後,我們必須提交或者撤銷所有的修改,並且在FINALLY子句和TRY/CATCH塊中關閉連接:
finally
{
#sql [myContext1] { commit };
myContext1.close();
#sql [myContext2] { commit };
myContext2.close();
}
...
迭代器(iterator)
iterator對象用於保存數據,在SQLJ程序中,SQL查詢返回的結果集也可以用iterator對象表示。iterator對象是iterator類的實例,概念類似於PL/SQL的遊標。
要使用iterator處理SQL查詢返回的行(rows,可以理解爲記錄),必須完成以下五步:
1.聲明iterator類。
2.聲明一個來自iterator類的iterator對象。
3.使用一個SELECT語句填充iterator對象。
4.從iterator對象讀出各行。
5.關閉iterator對象。
有兩種iterator類:
1.命名(Named)iterators,必須指定Java變量類型和iterator列名(column name)。
2.定位(Positional)iterators,只有被檢索的數據庫列的Java變量必須被指定。
命名Iterators:一個命名iterator聲明同時指定列的訪問名稱和他們的Java變量。
下面讓我們用例子示範所有五個步驟,假設想檢索Emp表的Ename、Job和HireDate列,用於查看薪水超過1500的職員。
1.聲明iterator類:
#sql iterator EmpIteratorClass(String Ename, String Job, Timestamp HireDate);
Java String類被用於表示Ename和Job列,因爲它與Oracle VARCHAR2數據庫的數據類型兼容。java.sql.Timestamp類型被用於表示HireDate列(Oracle的DATE數據類型),因爲java.sql.Date類型只能保存年、月、日和時間信息,不能像java.sql.Timestamp一樣可以保存時、分和秒。
2.聲明一個來自iterator類的iterator對象:
EmpIteratorClass empIterator;
3.使用一個SELECT語句填充iterator對象。下面的SQLJ語句用Emp表的Ename、Job和HireDate列填充empIterator對象:
int salary = 1500;
#sql empIterator = {
select Ename, Job, HireDate
from Emp
where Sal > :salary
};
我們也聲明主機變量salary,它被用於WHERE子句,識別從Emp表檢索的行。記住,SQL查詢返回的數據庫列的名字必須與在第一步定義的iterator列名相符。
4.從iterator對象讀出各行。因爲iterator對象可以包含多個行,我們必須使用循環依次訪問每一行――我們從PL/SQL遊標讀出每一行時,使用了相同的方法。命名iterator 實現了一個next()方法,調用它可以從頭到尾移動iterator對象的行。另外,SQLJ提供了檢索iterator值的方法。
下面的代碼循環打印姓名、工作和僱傭的日期:
while (empIterator.next()) {
System.out.println("Name: " + empIterator.Ename());
System.out.println("Job: " + empIterator.Job());
System.out.println("Hire Date:" + empIterator.HireDate().toString());
}
5.關閉iterator對象:
empIterator.close();
清單1結合了第二到第五步,通過命名iterator和empSalary參數展示listEmployees()方法。
清單1. Iterator類聲明在listEmployees()方法的外部
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);
}
}
定位Iterators:同命名iterators相比,定位iterators僅僅指定列的編號(number)和類型,而不指出他們的名字。列數據可以僅僅通過他們的位置來訪問,同傳統的(指PL/SQL)FETCH…INTO語法完全相同。另外,使用FETCH語句,你也必須使用定位iterator的方法endFetch()來探測終止的條件以跳出循環。在取得的數據被訪問前,總是必須檢查這個條件。
下面是使用定位iterator的例子時相同的五個步驟:
1.聲明iterator類:
#sql iterator EmpIteratorClass(String, String, Timestamp);
2.聲明一個來自iterator類的iterator對象,同時聲明所有必須的主機變量,用於從iterator對象取得數據:
EmpIteratorClass empIterator;
String name = null;
String job = null;
Timestamp hireDate = null;
3.使用一個SELECT語句填充iterator對象:
int salary = 1500;
#sql empIterator = {
select Ename, Job, HireDate
from Emp
where Sal > :salary
};
4.從iterator對象讀出各行,保存到主機變量:
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());
}
5.關閉iterator對象:
empIterator.close();
清單2結合了第二到第五步,通過定位iterator和empSalary參數展示listEmployees()方法。
清單2. 使用定位Iterator和empSalary參數的listEmployees()方法:
public static void listEmployees(String empSalary)
throws SQLException {
EmpIteratorClass empIterator;
Integer salary = new Integer(empSalary);
/* Host variables */
String name = null;
String 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);
}
}
如你所見,我們使用與PL/SQL遊標語法非常相似的語法操縱了全部的定位iterator對象。命名iterators和定位iterators都完成相同的基本功能:保存SQL查詢返回的多行結果集。使用哪種類型的iterator是一件麻煩事,或者說出於個人的偏愛。從性能的觀點看,他們產生同樣的結果。
可執行語句
可執行的SQLJ語句包含一個位於大括號之內的靜態SQL操作。有兩種可能的可執行語句類型,這取決於SQL是否返回值。
這是一個沒有返回值的嵌入SQL語句的例子,它在Emp表中創建一個關於Ename和Sal列的複合索引:
#sql { create index EMP_ENAME_SAL on Emp(Ename, Sal) };
如果嵌入SQL語句返回值,必須使用主機變量,以指定結果放在哪裏。在這個例子中,調用PL/SQL 的getSalary函數返回Empno=7900的僱員的薪水。你可以使用VALUES或SET操作符調用函數,即:
int salary;
int empNo = 7900;
#sql salary = { VALUES getSalary(:empNo) };
或者;
#sql { SET :salary = getSalary(:empNo) };
主機表達式
主機變量,如你在前面的例子中所見,允許SQLJ程序在數據庫和Java程序之間交換數據。他們是在Java程序中聲明並且在SQLJ語句中引用的任何Java變量。主機變量使用冒號前綴嵌入進SQLJ語句,並被主機表達式調用。他們綁定主機變量到SQLJ可執行語句,並且也可以包含Java數組元素、對象屬性或者Java函數,SQLJ將處理在SQL和Java環境之間來回地移動的數據。
在SQLJ中,所有標準的JDBC類型――如Boolean、byte、short、int、String、byte〔〕、double、float、java.sql.Data等等――都是有效的主機表達式類型。另外,Oracle的SQLJ翻譯器支持使用Oracle類型,如ROWID、CLOB、BLOB,以及Object和REF類型。
在這篇文章中,我講述了所有主要的SQLJ類型的對象,要開始寫實際的SQLJ代碼,這些都是必不可少的:連接環境、命名和定位迭代器(iterators)、可執行語句和主機表達式。
Boris Milrud 有十年的軟件開發經驗,他是位於San Jose CA(聖何塞市)Callidus軟件公司的高級數據庫工程師,他專長於各種Oracle數據庫軟件開發,包括數據庫設計、編程、優化和調試。可以通過[email protected]和他聯繫。
SQLJ程序是嵌入了SQL語句的標準Java程序,這些SQL語句從#sql標記開始,到分號結束。有兩種SQLJ語句:聲明和可執行語句。
聲明語句宣佈連接環境和迭代器。連接環境被用於建立數據庫連接,而迭代器被用於保存SQL查詢的結果集。這二者中,後者被用得更多。可執行語句執行嵌入的SQL語句和PL/SQL塊。因爲SQLJ程序要被翻譯和通過JDBC運行,所以任何被JDBC驅動程序支持的SQLJ語句(參加下面內容)都可以被嵌入進SQLJ可執行語句。可執行語句也可以包含主機表達式,通過Java變量,它被用來在Java程序和數據庫之間交換信息。
Oracle JDBC驅動程序
Oracle提供下面的JDBC驅動程序:
客戶端廋驅動程序是一個100%的Java驅動程序,由沒有安裝Oracle的客戶端使用,Oracle推薦通過applets使用它。當Java applet運行時,瀏覽器能夠下載這些驅動程序。
OCI驅動程序(OCI8和OCI7)通過Oracle客戶安裝,客戶端使用這些驅動程序。Oracle JDBC OCI驅動程序通過從Java直接調用Oracle調用接口(OCI)訪問數據庫,提供Oracle 7、8和8i版本之間最高的兼容性。這些驅動程序需要通過Net8的Oracle客戶安裝。
服務器端廋驅動程序提供同客戶端廋驅動程序相同的功能,但是它運行在Oracle數據庫內部,並且可以訪問遠程數據庫。作爲一箇中間件,從一個Oracle服務器訪問一個遠程的Oracle服務器,或者更一般地,從內部其它的Oracle服務器,如從任何Java存儲過程或EJB訪問一個Oracle服務器時,這種驅動程序很有用。
服務器端內部驅動程序,叫做KPRB(核心程序包),支持運行在完成SQL操作的目標Oracle數據庫內部的任何Java代碼。服務器端內部驅動程序允許JServer JVM直接同SQL引擎通訊,在Oracle 8i/9i服務器上,它是作爲存儲過程、存儲函數、中間件、EJB或者CORBA對象運行的Java代碼的默認JDBC驅動程序。
由於運行在Oracle JServer內部,同時被專門優化,KPRB JDBC驅動程序非常靈活和有效率。你在寫SQLJ存儲過程時,將會用到它。
接下來,讓我們分析描述的SQLJ元素:連接環境、迭代器、可執行語句和主機表達式。
連接環境
要建立一個簡單的連接,在構造DefaultContext對象的時候,可以使用一個DefaultContext的實例,指定數據庫的URL、用戶名和密碼來實現,而最容易的方法是使用由Oracle公司提供的oracle.sqlj運行時的connect()方法。在下面的例子中,我們將使用JDBC廋驅動程序,通過1521端口把用戶“scott”連接到服務器MYSERVER的數據庫,密碼是“tiger”,ORCL是被連接的數據庫的SID:
Oracle.connect("jdbc:oracle:thin@MYSERVER:1521:ORCL", "scott", "tiger");
這個例子創建DefaultContext類的一個實例,並且把它安裝爲你默認的連接。有了這個DefaultContext的實例,不再需要其他的任何東西。
要建立多個連接,可以通過Oracle.getConnection()方法,創建和使用DefaultContext類另外的實例。在在下面的例子中,我們將使用Oracle OCI8驅動程序,把MYSERVER_ORCL作爲一個Oracle服務器的名字,在TNSNames.ora中創建一個ORCL實例:
DefaultContext myContext1 = Oracle.getConnection
("jdbc:oracle:oci8@MYSERVER_ORCL", "scott", "tiger");
DefaultContext myContext2 = Oracle.getConnection
("jdbc:oracle:oci8@MYSERVER_ORCL ", "tom", "bear");
這段代碼創建兩個連接環境實例,他們都使用相同的Oracle OCI8驅動程序,但是使用不同的schemas。通過爲每一個語句指定連接,在這兩個schemas中都可以完成SQL操作:
#sql [myContext1] { SQL statement };
...
#sql [myContext2] { SQL statement };
在程序的最後,我們必須提交或者撤銷所有的修改,並且在FINALLY子句和TRY/CATCH塊中關閉連接:
finally
{
#sql [myContext1] { commit };
myContext1.close();
#sql [myContext2] { commit };
myContext2.close();
}
...
迭代器(iterator)
iterator對象用於保存數據,在SQLJ程序中,SQL查詢返回的結果集也可以用iterator對象表示。iterator對象是iterator類的實例,概念類似於PL/SQL的遊標。
要使用iterator處理SQL查詢返回的行(rows,可以理解爲記錄),必須完成以下五步:
1.聲明iterator類。
2.聲明一個來自iterator類的iterator對象。
3.使用一個SELECT語句填充iterator對象。
4.從iterator對象讀出各行。
5.關閉iterator對象。
有兩種iterator類:
1.命名(Named)iterators,必須指定Java變量類型和iterator列名(column name)。
2.定位(Positional)iterators,只有被檢索的數據庫列的Java變量必須被指定。
命名Iterators:一個命名iterator聲明同時指定列的訪問名稱和他們的Java變量。
下面讓我們用例子示範所有五個步驟,假設想檢索Emp表的Ename、Job和HireDate列,用於查看薪水超過1500的職員。
1.聲明iterator類:
#sql iterator EmpIteratorClass(String Ename, String Job, Timestamp HireDate);
Java String類被用於表示Ename和Job列,因爲它與Oracle VARCHAR2數據庫的數據類型兼容。java.sql.Timestamp類型被用於表示HireDate列(Oracle的DATE數據類型),因爲java.sql.Date類型只能保存年、月、日和時間信息,不能像java.sql.Timestamp一樣可以保存時、分和秒。
2.聲明一個來自iterator類的iterator對象:
EmpIteratorClass empIterator;
3.使用一個SELECT語句填充iterator對象。下面的SQLJ語句用Emp表的Ename、Job和HireDate列填充empIterator對象:
int salary = 1500;
#sql empIterator = {
select Ename, Job, HireDate
from Emp
where Sal > :salary
};
我們也聲明主機變量salary,它被用於WHERE子句,識別從Emp表檢索的行。記住,SQL查詢返回的數據庫列的名字必須與在第一步定義的iterator列名相符。
4.從iterator對象讀出各行。因爲iterator對象可以包含多個行,我們必須使用循環依次訪問每一行――我們從PL/SQL遊標讀出每一行時,使用了相同的方法。命名iterator 實現了一個next()方法,調用它可以從頭到尾移動iterator對象的行。另外,SQLJ提供了檢索iterator值的方法。
下面的代碼循環打印姓名、工作和僱傭的日期:
while (empIterator.next()) {
System.out.println("Name: " + empIterator.Ename());
System.out.println("Job: " + empIterator.Job());
System.out.println("Hire Date:" + empIterator.HireDate().toString());
}
5.關閉iterator對象:
empIterator.close();
清單1結合了第二到第五步,通過命名iterator和empSalary參數展示listEmployees()方法。
清單1. Iterator類聲明在listEmployees()方法的外部
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);
}
}
定位Iterators:同命名iterators相比,定位iterators僅僅指定列的編號(number)和類型,而不指出他們的名字。列數據可以僅僅通過他們的位置來訪問,同傳統的(指PL/SQL)FETCH…INTO語法完全相同。另外,使用FETCH語句,你也必須使用定位iterator的方法endFetch()來探測終止的條件以跳出循環。在取得的數據被訪問前,總是必須檢查這個條件。
下面是使用定位iterator的例子時相同的五個步驟:
1.聲明iterator類:
#sql iterator EmpIteratorClass(String, String, Timestamp);
2.聲明一個來自iterator類的iterator對象,同時聲明所有必須的主機變量,用於從iterator對象取得數據:
EmpIteratorClass empIterator;
String name = null;
String job = null;
Timestamp hireDate = null;
3.使用一個SELECT語句填充iterator對象:
int salary = 1500;
#sql empIterator = {
select Ename, Job, HireDate
from Emp
where Sal > :salary
};
4.從iterator對象讀出各行,保存到主機變量:
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());
}
5.關閉iterator對象:
empIterator.close();
清單2結合了第二到第五步,通過定位iterator和empSalary參數展示listEmployees()方法。
清單2. 使用定位Iterator和empSalary參數的listEmployees()方法:
public static void listEmployees(String empSalary)
throws SQLException {
EmpIteratorClass empIterator;
Integer salary = new Integer(empSalary);
/* Host variables */
String name = null;
String 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);
}
}
如你所見,我們使用與PL/SQL遊標語法非常相似的語法操縱了全部的定位iterator對象。命名iterators和定位iterators都完成相同的基本功能:保存SQL查詢返回的多行結果集。使用哪種類型的iterator是一件麻煩事,或者說出於個人的偏愛。從性能的觀點看,他們產生同樣的結果。
可執行語句
可執行的SQLJ語句包含一個位於大括號之內的靜態SQL操作。有兩種可能的可執行語句類型,這取決於SQL是否返回值。
這是一個沒有返回值的嵌入SQL語句的例子,它在Emp表中創建一個關於Ename和Sal列的複合索引:
#sql { create index EMP_ENAME_SAL on Emp(Ename, Sal) };
如果嵌入SQL語句返回值,必須使用主機變量,以指定結果放在哪裏。在這個例子中,調用PL/SQL 的getSalary函數返回Empno=7900的僱員的薪水。你可以使用VALUES或SET操作符調用函數,即:
int salary;
int empNo = 7900;
#sql salary = { VALUES getSalary(:empNo) };
或者;
#sql { SET :salary = getSalary(:empNo) };
主機表達式
主機變量,如你在前面的例子中所見,允許SQLJ程序在數據庫和Java程序之間交換數據。他們是在Java程序中聲明並且在SQLJ語句中引用的任何Java變量。主機變量使用冒號前綴嵌入進SQLJ語句,並被主機表達式調用。他們綁定主機變量到SQLJ可執行語句,並且也可以包含Java數組元素、對象屬性或者Java函數,SQLJ將處理在SQL和Java環境之間來回地移動的數據。
在SQLJ中,所有標準的JDBC類型――如Boolean、byte、short、int、String、byte〔〕、double、float、java.sql.Data等等――都是有效的主機表達式類型。另外,Oracle的SQLJ翻譯器支持使用Oracle類型,如ROWID、CLOB、BLOB,以及Object和REF類型。
在這篇文章中,我講述了所有主要的SQLJ類型的對象,要開始寫實際的SQLJ代碼,這些都是必不可少的:連接環境、命名和定位迭代器(iterators)、可執行語句和主機表達式。
Boris Milrud 有十年的軟件開發經驗,他是位於San Jose CA(聖何塞市)Callidus軟件公司的高級數據庫工程師,他專長於各種Oracle數據庫軟件開發,包括數據庫設計、編程、優化和調試。可以通過[email protected]和他聯繫。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.