在我的第一篇文章中,我描述了什么是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数据库软件开发,包括数据库设计、编程、优化和调试。可以通过milrud@hotmail.com和他联系。
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数据库软件开发,包括数据库设计、编程、优化和调试。可以通过milrud@hotmail.com和他联系。