1. 編寫一個SQLJ存儲過程
3. 在數據庫中發佈Java方法
4. 運行過程
5. 和PL/SQL相對應的過程比較結果
PL/SQL存儲過程
DeleteClassification過程主要使用兩個表工作,ValidTransaction (父表)和Classification (子表),是一種使用TransactionId外鍵的一到多的關係。 存儲過程的目的就是刪除所有指定的時期/月份的ValidTransaction的Classification紀錄。 ValidTransaction表中的TransactionDate字段選擇這個日期範圍之內的記錄。
在例子中,ValidTransaction包含12個時期的數據,共有一百二十萬行。 在ValidTransaction表中的每一行,Classification表分別有10行與之對應,所以就共有一千二百萬行數據。清除一個指定月份的Classification數據意味着從Classification表中刪除1百萬行數據。
DeleteClassification過程的代碼使用了一些Oracle 8i PL/SQL中的新的特性,象BULK BINDS和BULK COLLECT,用來改善事務的執行性能並增加你在事務中可交付的數據數量,從而減少事務的數目並且降低發生ORA - 01555錯誤的可能性。 BULK COLLECT子句能使整個TransactToReset遊標一步就被取出到v_TransactionId PL/SQL表中。 然後遊標立即被關閉以避免交叉獲取錯誤,這樣也同樣減少ORA - 01555錯誤的發生。
最後,這個過程計算刪除行數的百分比。 如果結果超過了限度,主關鍵字索引可能就會被損壞而且你還可能要重建它。
代碼段1 演示了DeleteClassification存儲過程的PL/SQL版本。
創建SQLJ存儲過程將要編寫服務器端SQLJ代碼,另外將在一臺客戶機上執行編譯過程。這個途徑提供翻譯SQLJ代碼的最大靈活性--選項設置和錯誤處理在客戶機上比在服務器上更方便。
開發一個SQLJ存儲過程由五個步驟組成:
1. 使用Java類的靜態方法創建一個SQLJ源文件,後面將要把這個源文件通過SQL封裝器與PL/SQL環境合併起來。
2. 把這個SQLJ源文件編譯成一個Java類或者一組Java類。把所有的類合併成一個.jar文件。
3. 使用loadjava實用程序把這個.jar文件裝載入Oracle數據庫服務器。 確定裝載過程是成功的,爲每個Java類都創建一個Java Class類型的模式對象。
4. 爲存儲的Java過程/函數創建一個PL/SQL封裝器。
5. 通過從SQL * Plus或者PL/SQL上調用存儲的Java過程來測試這些過程。
使用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."); } } |