第一步: 创建SQLJ源文件
使用import语句启动PipelineReset.sqlj文件。 只导入你计划在你的类中使用的Java类和程序包。
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Iterator;
|
SQLJ运行时刻程序包可在JServer Java虚拟机( JVM)中找到。 还有,你不必编写创建到数据库的连接的代码了,因为JServer本身带有到数据库服务器的隐含的连接。由于你将在客户机上编译源代码,所以必须包括下列三个import语句:
import oracle.jdbc.driver.OracleConnection;
import sqlj.runtime.ref.DefaultContext;
import sqlj.runtime.ExecutionContext; |
接下来,需要声明迭代程序类。在迭代程序类声明中定义了SQLJ为你构造的Java类。你必须把SQLJ迭代程序声明和连接上下文说明放在你的SQLJ源文件里的Java类的顶端,紧接着import语句之后。
下列三个迭代程序声明提供PL/SQL版的三个游标函数:
#sql iterator StartEndDate
(Timestamp startDate, Timestamp endDate);
#sql iterator ValidTransactions
(Long transactionId);
#sql iterator ClassificationCount
(long); |
使用下面三个参数定义PipelineReset.java类的静态DeleteClassification方法:
public class PipelineReset {
public static void DeleteClassification(
String periodName,
int commitBatchSize,
int percentageDeleted) throws SQLException {
...
}
} |
现在我们把代码放入方法中。 首先你需要实例化一个迭代程序对象决定具体时期的起始和结束时间。 使用periodname参数:
StartEndDate dateIter;
Timestamp endDate = null;
Timestamp startDate = null;
#sql dateIter = { select startDate,
endDate
from Period
where description = :periodName };
if (dateIter.next()) {
startDate = dateIter.startDate();
endDate = dateIter.endDate();
};
dateIter.close(); |
StartEndDate dateIter;下一步是实例化ValidTransactions类型的迭代程序对象并把你的具体时期内全部的TransactionId都放入ArrayList类型的vtIdList对象中。
在PL/SQL版本中,这个工作是由游标csrTransactToReset完成的。
ValidTransactions vtIter;
#sql vtIter = { select T.TransactionId
from ValidTransaction T
where T.TransactionDate >= :startDate
and T.TransactionDate <= :endDate
and exists (select '1'
from Classification C
where C.TransactionId = T.TransactionId) };
ArrayList vtIdList = new ArrayList();
while (vtIter.next()) {
vtIdList.add((Long) vtIter.TransactionId());
}
vtIter.close(); |
现在在vtIDList对象中循环,删除Classification中TransactionId每个值所对应的行。
ExecutionContext ec = DefaultContext.getDefaultContext().getExecutionContext();
ClassificationCount countIter;
long vDeletedRows = 0;
long vTotalRows = 0;
int vCount = 0;
if (vtIdList.size() > 0) {
#sql countIter = { select count(*) from Classification };
#sql { fetch : countIter into :vTotalRows };
countIter.close();
Iterator itr = vtIdList.iterator();
while (itr.hasNext()) {
vCount++;
#sql [ec] { delete from Classification
where TransactionId = :((Long) itr.next()) };
vDeletedRows = vDeletedRows + ec.getUpdateCount();
if ((vCount % commitBatchSize) == 0) {
#sql [ec] { commit };
}
}
#sql [ec] { commit };
}
|
最后,你必须决定你是否要重建索引。 从你的SQLJ代码中调用相同的RebuildIndex PL/SQL存储过程:
if ((((float) vDeletedRows / (float) vTotalRows)
* 100) >=
percentageDeleted ) {
#sql [ec] {call RebuildIndex('CLASSIFICATION_PK_IND',
'parallel nologging compute statistics')};
} |
代码清单2 显示了PipelineReset.sqlj文件的完整的清单。
第二步: 翻译并编译SQLJ源文件
SQLJ翻译器通过检查不正确的SQLJ语法执行对SQLJ源文件的分析。然后调用一个可执行SQLJ语句的语义检验程序。如果在sqlj.exe命令中指定user
= userName/password选项,语义检验程序将使用这个信息来连接数据库并检验存在的数据库表和用于SQLJ源文件的存储过程。随后SQLJ翻译器为SQLJ源文件中的每个连接类生成Java源文件和一个单独的配置文件。Java虚拟机调用编译程序(
javac)把所有的.java文件编译为.class类文件。 最后,JVM调用Oracle SQLJ定制程序把生成的配置文件转换为Oracle特定的组件。
本文中不可能包含所有的SQLJ实用命令行选项,但是如果你需要更多信息,请参阅相应的Oracle文档。
我发现把下面的信息放入名为Translator.properties的属性文件中会很方便:
sqlj.user=scott
sqlj.password=tiger
sqlj.driver=oracle.jdbc.driver.OracleDriver
sqlj.url=jdbc:oracle:oci8:@ORCL
sqlj.warn=verbose
sqlj.linemap |
你可以在OracleHome/sqlj/demo目录中找到一个样本sqlj.properties文件。下面给出了用来翻译和编译SQLJ源文件的命令:
sqlj -props=translator.properties -explain -ser2class
-status PipelineReset.sqlj |
推荐在SQLJ命令行中包括- explain和- status选项,这样就能显示并很容易了解翻译和编译步骤。
使用- ser2class标志把生成的配置文件(.ser文件)转换成.class文件。
在本例中,用六个类文件结束:主要的PipelineReset.class,三个迭代程序类( StartEndDate.class,ValidTransactions.class和ClassificationCount.class)以及配置类(
PipelineReset_SJProfile.class和PipelineReset_SJProfileKeys.class)。使用下列命令把它们封装到一个PipelineReset.jar
Java压缩文件中:
jar cvf0 PipelineReset.jar *.class |
现在需要把生成的类载入数据库中。
第三步:把Java文件装载入数据库服务器
Oracle提供了一个默认的使用Oracle JDBC OCI8驱动程序的loadjava实用程序来完成连接。下面就是用一个ORCL数据库和一个用户scott/tiger做为参数来使用这个实用程序的语法:
loadjava -user scott/tiger@ORCL -resolve PipelineReset.jar
|
还可以指定Oracle JDBC瘦驱动程序:
loadjava -thin -user scott/tiger@server:1521:ORCL
-resolve PipelineReset.jar |
使用- resolve标志直接地分析类的引用而不是等待每个类被第一次调用。
在User_Objects Oracle词典中运行查询,使loadjava把所有的类都成功加载。
select object_name,
status
from User_Objects
where object_type = 'JAVA CLASS'
order by object_name; |
下面是上面查询的输出结果:
OBJECT_NAME STATUS
------------------------------ -------
ClassificationCount VALID
PipelineReset VALID
PipelineReset_SJProfile0 VALID
PipelineReset_SJProfileKeys VALID
StartEndDate VALID
ValidTransactions VALID
6 rows selected. |
正如所见,你所有的Java类都被成功地载入数据库中了。
第四步:为Java存储过程创建PL/SQL封装程序
即使Java代码已经在数据库中了,但是它还是不能真正的可用。 Oracle数据库不能直接调用Java类方法。 它通过PL/SQL间接调用它们并把它们传到JServer
Java虚拟机中来运行。这就是为什么需要为PipelineReset类的DeleteClassification静态方法创建一个PL/SQL封装程序(也就是在数据库服务器中发布Java方法)。
下面的脚本将创建PipelineResetFull程序包的一个JDeleteClassification过程。 这个过程是PipelineReset
Java类的DeleteClassification静态方法的PL/SQL封装程序。
create or replace package PipelineResetFull as
procedure JDeleteClassification
(periodName in varchar2,
commitBatchSize in number,
percentDeleted in number);
end PipelineResetFull;
/
create or replace package body PipelineResetFull as
procedure JDeleteClassification
(periodName in varchar2,
commitBatchSize in number,
percentDeleted in number)
as
language java
name 'PipelineReset.DeleteClassification(java.lang.String,
int,
int)';
end PipelineResetFull;
/ |
例如:下列的语法是错误的:
...
name 'PipelineReset.DeleteClassification(String,
int,
int)';
...
|
它能够编译,但是将抛出运行时间异常:
ERROR at line 1:
ORA-29531: no method DeleteClassification in class PipelineReset
|
这是怎么发生的呢?DeleteClassification不就在PipelineReset类中吗? 事实上,它是这个类中的唯一的方法!
Oracle不能匹配PL/SQL封装程序中的DeleteClassification方法与一个实际类的方法。 在这种情况下,你必须明确地指定一个完全符合规格的类的路径,除非它是一个基类,就像在我们例子中的int
Java类型。
第五步:测试Java存储过程
在数据库中发布Java代码之后,我们就会发现在PL/SQL和Java过程之间就没有用法差异了。可以使用下列代码从SQL * Plus中调用并测试PL/SQL和Java代码:
begin
DeleteClassification('January 1999', 10000, 10);
End;
/
begin
PipelineResetFull.JDeleteClassification('January 1999', 10000,
10);
end;
/ |
现在我们甚至不能分辩出哪个是Java过程,因为它已经完全和PL/SQL环境整合起来了。
SQLJ:一个有价值的数据库开发解决方案
出于本文的教学目的,我有意从一个简单的SQLJ程序开始谈起,而没有直接进入更加复杂的内容。 SQLJ的改善执行性能的特点,比如行预取指令和批处理能力,允许SQLJ开发者从多个块中的数据库检索数据行,并把SQL语句成批地发送回数据库中。
我认为SQLJ是一个非常有价值的数据库开发解决方案,将不断的发展和成熟。 ORACLE 9i最近在SQLJ中引入动态SQL查询语言,这样使“SQLJ是嵌入Java中的静态SQL查询语言”这句话称为了历史。那意味着SQLJ不再需要使用庞大的JDBC代码来处理动态的SQL,因为它可以处理并使用静态的和动态的SQL,而且可以调用象PL/SQL这样的数据库专用程序。
代码段1
create or replace procedure DeleteClassification
(pPeriodName in varchar2: = null,
pCommitBatchSize in integer := 10000,
pPercentDeleted in integer:= 10)
as
vPeriodSeq pls_integer;
vPeriodStart date;
vPeriodEnd date;
vBeginIndex pls_integer := 0;
vEndIndex pls_integer := 0;
vTransactionIdCount integer := 0;
vDeletedRows integer := 0;
vTotalRows integer := 0;
vPercentageDeleted pls_integer := 0;
cursor csrPeriodData (pPeriodDescription Period.Description%type)
is
select PeriodSeq,
StartDate,
EndDate
from Period
where Description = pPeriodDescription;
type tTransactionId is
table of ValidTransaction.TransactionId%type
index by binary_integer;
vTransactionId tTransactionId;
cursor csrTransactToReset (pStartDate IN date, pEndDate IN date)
IS
select T.TransactionId
from ValidTransaction T
where T.TransactionDate >= pStartDate
and T.TransactionDate <= pEndDate
and exists
(select '1'
from Classification C
where C.TransactionId = T.TransactionId);
cursor csrClassificationCount is
select count(*)
from Classification;
begin
if pPeriodName is null
then
RAISE_APPLICATION_ERROR(-20301,
'You must supply a valid period.');
end if;
open csrPeriodData(pPeriodName);
fetch csrPeriodData into vPeriodSeq, vPeriodStart, vPeriodEnd;
if csrPeriodData%notfound
then
close csrPeriodData;
RAISE_APPLICATION_ERROR(-20302,
'You must supply a valid period. ' ||
'Please use the description field of the period.');
end if;
close csrPeriodData;
open csrTransactToReset (vPeriodStart, vPeriodEnd);
fetch csrTransactToReset bulk collect into vTransactionId;
close csrTransactToReset;
vTransactionIdCount := vTransactionId.count;
if vTransactionIdCount > 0
then
open csrClassificationCount;
fetch csrClassificationCount into vTotalRows;
close csrClassificationCount;
while vEndIndex < vTransactionIdCount
loop
vBeginIndex := vEndIndex + 1;
if vEndIndex + pCommitBatchSize < vTransactionIdCount
then
vEndIndex := vEndIndex + pCommitBatchSize;
else
vEndIndex := vTransactionIdCount;
end if;
forall nIndex in vBeginIndex..vEndIndex
delete from Classification
where TransactionId = vTransactionId(nIndex);
vDeletedRows := vDeletedRows + sql%rowcount;
commit;
end loop;
vPercentageDeleted := round((vDeletedRows / vTotalRows) * 100);
if vPercentageDeleted >= p_percentDeleted
then
RebuildIndex('CLASSIFICATION_PK_IND',
'parallel nologging compute statistics');
end if;
end if;
end DeleteClassification; |
代码段2:
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Iterator;
import oracle.jdbc.driver.OracleConnection;
import sqlj.runtime.ref.DefaultContext;
import sqlj.runtime.ExecutionContext;
#sql iterator StartEndDate (Timestamp startDate, Timestamp endDate)
;
#sql iterator ValidTransactions (Long transactionId) ;
#sql iterator ClassificationCount (long) ;
public class PipelineReset
{
public static void DeleteClassification(
String periodName,
int commitBatchSize,
int percentageDeleted) throws SQLException {
StartEndDate dateIter;
ValidTransactions vtIter;
ClassificationCount countIter;
Timestamp endDate = null;
Timestamp startDate = null;
int vCount = 0;
long vDeletedRows = 0;
long vTotalRows = 0;
#sql dateIter = { select startDate,
endDate
from Period
where description = :periodName };
long startTime = System.currentTimeMillis();
if (dateIter.next()) {
startDate = dateIter.startDate();
endDate = dateIter.endDate();
};
dateIter.close();
#sql vtIter = { select T.TransactionId
from ValidTransaction T
where T.TransactionDate >= :startDate
and T.TransactionDate <= :endDate
and exists (select '1'
from Classification C
where C.TransactionId = T.TransactionId) };
fetched from vtIter iterator */
ArrayList vtIdList = new ArrayList();
while (vtIter.next()) {
vtIdList.add((Long) vtIter.TransactionId());
}
vtIter.close();
ExecutionContext ec =
DefaultContext.getDefaultContext().getExecutionContext();
if (vtIdList.size() > 0) {
#sql countIter = { select count(*) from Classification };
#sql { fetch : countIter into :vTotalRows };
countIter.close();
Iterator itr = vtIdList.iterator();
while (itr.hasNext()) {
vCount++;
#sql [ec] { delete from Classification
where TransactionId = :((Long) itr.next()) };
vDeletedRows = vDeletedRows + ec.getUpdateCount();
if ((vCount % commitBatchSize) == 0) {
#sql [ec] { commit };
}
}
#sql [ec] { commit };
}
if ((((float) vDeletedRows / (float) vTotalRows) * 100) >=
percentageDeleted ) {
#sql [ec] { call RebuildIndex('CLASSIFICATION_PK_IND',
'parallel nologging compute statistics') };
}
}
long endTime = System.currentTimeMillis();
System.out.println("Total time : " +
((endTime - startTime) / 1000) + " sec.");
}
}
|