參考:程序包調用報ORA-06508: PL/SQL: 無法找到正在調用的程序單元
出現這種情況的原因是因為,對於全局變量,每一個session會生成一個本地copy,如果程序重新編譯的話,就會因程序里原變量找不到而丟棄該變量,繼而導致這個錯誤。
也就是說在一個會話中調用程序包package時,會生成package中全局變量的副本,如果在另一個會話中對此package進行編譯就會使前一個會話中的副本失效,故而產生錯誤。
要想避免這個錯誤,可以使程序捕獲ORA-06508:的錯誤進行處理,也可以重新初始化會話
前幾天在生產環境編譯了一個ORACLE 包,導致了OA接口中拋出ORA-06508的異常。
經檢查,是由於OA接口中調用的PACKAGE的依賴包中定義了全局變量,若在package中定義了全局變量,該包被編譯過但是應用沒有重啟或者沒有刷新連接池,則會導致 "錯誤ORA-06508: PL/SQL: 無法找到正在調用 的程序單元"。
1.若PACKAGE中定義了全局變量或者全局常量,則此類包被稱為有狀態的包。
2.若有狀態的包編譯了,則上層應用連接池會認為該包無效了。
3.解決方式有兩種
a.)簡單粗暴的,直接重啟上層應用。
b.)盡量不要在包中定義全局變量或者全局常量,若必須定義,則將全局變量或者全局常量定義到伴生包中。
參考資料:在Java/JDBC中透明處理“ORA-04068”錯誤
個人猜測翻譯自 Dealing with Oracle PL/SQL Error "ORA-04068: existing state of packages has been discarded" Transparently in Java/JDBC(需F|Q)
導言
在我們需要與數據庫進行交互時,應盡可能地使用存儲過程——無論我們使用哪個數據庫。這是假設這個數據庫提供了編寫存儲過程的工具,大多數主要的數據庫都確實如此,例如Oracle、MySQL和SQL Server。而且無論你是使用Java、.NET或任何其它的編程語言或框架。
在Oracle 里,如果你想編寫存儲過程你當然應該使用PL/SQL包。在這篇文章里,假設你一般了解PL/SQL 和非常熟悉PL/SQL 包。這篇文章關注於一個令人討厭的錯誤,這個錯誤使許多使用PL/SQL以及使用API(例如JDBC)從應用層調用它的開發人員很苦惱。這個錯誤就是“ORA-04068: existing state of packages has been discarded”。這個錯誤是當Oracle認為你的包狀態出於某種原因是無效的時候拋出的。在這篇文章里,我們將討論:
“ORA-04068”錯誤是什么和它為什么發生,它會影響什么,以及建議的解決方法
下面我們將從定義“ORA-04068”錯誤開始。
注意: 在這篇文章的示例里使用的是Oracle 9.2.0.3,不過相同的概念在Oracle 10g 中應該也是適用的。
“ORA-04068”錯誤是什么和它為什么發生?
如果我們使用Oracle的oerr程序看看ORA-04068的定義,我們會得到下面的信息:
$oerr ora 04068 04068, 00000, "existing state of packages%s%s%s has been discarded" // *Cause: One of errors 4060 - 4067 when attempt to execute a stored procedure. // *Action: Try again after proper re-initialization of any // application's state.
這個錯誤顯示執行包的現有狀態被另一個會話的一個動作無效化了。這個“狀態”涉及包在規范或體中定義的任何全局變量(包括常量)。引起這個錯誤的動作一般是(但不局限於此)在得到了發生錯誤的會話所使用的連接之后包的重新編譯。Oracle 建議的動作是重新初始化應用程序狀態以調整包的新狀態后重新嘗試。
如果我們看些例子就會明白得多。
假設有下面定義的表:
create table t (x number );
有個叫做pkg 的包具有一個叫做p的存儲過程,如下所示:
create or replace package pkg as procedure p; end pkg; /
下面所顯示的包pkg的包體定義了存儲過程p只是插入一個常量1到我們先前定義的表t中
create or replace package body pkg as procedure p is begin insert into t(x) values (1); end p; end pkg; /
注意在包規范或包體中沒有全局變量或常量。換句話說,這個包是“無狀態的”。
我們將使用兩個SQL*Plus 會話來解釋這個概念。在每一個“體驗”中,我們會在每一個會話中編譯包體之后執行存儲過程pkg.p。現在開始體驗1,在體驗1中,即使我們在另一個會話中編譯包體也不會出現ORA-04068錯誤的。這是因為這個包是“無狀態的”,它在規范或體中沒有定義任何全局變量或常量。
體驗1
假設表t和包pkg的規范以及包體已經在包里定義了。在SQL*Plus 會話1中,我們執行包並獲得下面的結果(這個包執行成功)。
注意:你可能注意到在這篇文章里啟動SQL*Plus 有時和常規啟動("SQL >")不一樣——例如,在下面代碼的“session 1”中。例如這可以使用命令“set sqlprompt 'session 1”來實現。
session 1> exec pkg.p PL/SQL procedure successfully completed.
在SQL*Plus會話2中,我們通過像下面這樣重新創建包來重新編譯它:
session 2> create or replace package body pkg as procedure p is begin insert into t(x) values (1); end p; end pkg; / Package body created. session 2> show errors; No errors.
現在如果你回到會話1並重新執行包存儲過程p,它會成功執行。
session 1> exec pkg.p PL/SQL procedure successfully completed.
讓我們看看到目前為止我們所做的。我們定義了一個簡單的包,只具有一個插入一個常量到一個數據表中的存儲過程。我們開啟了一個會話並執行這個包存儲過程。在另一個會話中我們重新編譯這個包(通過重新創建它)。當我們在第一個會話中重新執行這個包時,它運行正常——特別是,在會話2中包的重新編譯在會話1中存儲過程的第二次執行沒有出現任何錯誤。
現在讓我們重復這整個過程,只改變一個地方——添加一個全局變量到包體中(添加到規范中是一樣的)。這意味着我們給包添加了“狀態”。我們在下一節“體驗2”中將講述只做了這一個改變的相同體驗。
體驗2
我們從之前的會話退出。開啟一個新的會話並在會話1中編輯我們新的包體,如下所示——注意在包的開始部分有一個常量聲明,如下面的粗體顯示。這是包的狀態。這個常量不會被使用,但是它是這個體驗沒有得到結果的原因。
session1>@pkg_body session1>create or replace package body pkg as g_constant constant number := 1; procedure p is begin insert into t(x) values (1); end p; end pkg; / Package body created. session1>show errors; No errors.
現在我們在會話1中執行存儲過程p。
session 1> exec pkg.p PL/SQL procedure successfully completed.
開啟一個新的會話“session 2”並通過重新創建這個包來重新編譯它。
session 2> @pkg_body session 2> create or replace package body pkg as g_constant constant number := 1; procedure p is begin insert into t(x) values (1); end p; end pkg; / Package body created. session 2> show errors;
再次在會話1中執行存儲過程p,得到下面的結果:
session1>exec pkg.p BEGIN pkg.p; END; * ERROR at line 1: ORA-04068: existing state of packages has been discarded ORA-04061: existing state of package body "ORA92.PKG" has been invalidated ORA-04065: not executed, altered or dropped package body "ORA92.PKG" ORA-06508: PL/SQL: could not find program unit being called ORA-06512: at line 1
發生了什么?當我們重新在會話2中編譯包體時,我們重置了包的狀態。換句話說,對於任何在包編譯之前連接的會話,包的執行狀態(在這種情況下,由在包體里指定給常量的值來定義)被從內存中刪除了。注意,我們實際上沒有改變這個狀態(我們在重新編譯的時候保持相同的常量值),但是Oracle沒有跟蹤這一級別上的細節。只要連接了Oracle,在會話2里,重新編譯了包pkg——這個包的狀態現在重置為一個新的狀態——所以對於任何在這個重新編譯發生之前已經連接到Oracle的已有會話來說,包的狀態變為無效。因此任何存儲過程或包的函數下一次執行時,就會立即拋出ORA-04068錯誤。
如果我們在第一次嘗試中得到了ORA-04068錯誤,那么我們在會話1中重新執行這個包會發生什么呢?讓我們看一下。
session 1> exec pkg.p PL/SQL procedure successfully completed.
如同你所看到的,第二次執行顯示調用應用程序(在這個例子中是SQL*Plus)調整為新的狀態(自從Oracle通知了一次改變的狀態)並重新執行有了新狀態的包。這是Oracle 所建議的動作(查看這一節的開始部分):
在恰當地重新初始化任何應用程序狀態之后重新嘗試。
下面講述ORA-04068錯誤的一些影響。
“ORA-04068”錯誤的影響
要測量ORA-04068的影響,你所要做的一切就是google它。它的兩個主要影響如下:
大多數企業應用程序使用緩存連接的連接池。現在無論何時部署一個新的包規范,都需要在生產過程中重新編譯。你編譯的時候,對於連接池中的所有連接,這個包的狀態都會被無效化,因為這個包在獲得連接之后進行了重新編譯(作為連接池初始化的一部分,有時更早)。注意,無論你是否改變這個狀態,無論你是否改變代碼,都會如此。當一個存儲過程或一個函數第一次被調用時,它將會失敗並拋出“ORA-04058”錯誤。所以一般情況下,你需要記住要“刷出”連接池(意味着丟棄現有連接並獲得到Oracle的新連接)。這通常導致應用程序部署的一個停機。例如,如果你正在使用tomcat 和在tomcat 中的一個連接池,那么你可能需要停止tomcat 並重啟——以便它重新初始化連接池。那么如果有一個長時間運行的批處理正在使用一個連接來執行與需要重新編譯的包完全無關的一些邏輯呢?那么你或者需要等待這個批處理執行完畢或者在部署過程中將它關閉以便你可以重新初始化連接池。正如你可能想到的,這在應用程序有效性方面會是個夢魘。
很糟糕的一個影響是開發人員會困惑於為什么一個簡單包(具有一個狀態)的重新編譯會導致在Oracle中得到這個錯誤。特別是在其它的數據庫例如SQL Server和MySQL沒有相同的包概念,因此沒有與存儲過程或函數相關聯的一個狀態。所以,在這些數據庫中,你可以重新部署存儲過程,並且應用程序會透明地使用它們。對於其它的數據庫這是否是一個正確的選擇是具有爭議的,並且不屬於本篇文章要討論的范圍。除了了解ORA-04068錯誤的根本原因以及怎樣處理它之外,這個錯誤還會使得開發人員放棄去一起使用存儲過程(並從而放棄了使用存儲過程所帶來的所有好處),並在他們的應用程序代碼中嵌入SQL語句(例如在Java代碼中嵌入SQL)。
那么解決方法是什么?
在這一節,我們將討論處理“ORA-04068”錯誤的許多解決方法。每一個解決方法都在它的可用性方面具有一些局限性。這些解決方法還顯示了思考的過程,使得更容易理解推薦的解決方法和替代方法。
讓我們從解決方法1開始。
解決方法1:使用無狀態包
最簡單的解決方法是在你的系統中只使用無狀態的包。正如我們在前面章句提到的,當你重新執行一個無狀態的包時即使是它在另一個會話中重新編譯之后也不會發生ORA-04068錯誤的。這是因為沒有可以被Oracle無效化的狀態。
這個解決方法,雖然在理論方面很簡單,但是具有以下明顯的缺陷:
它使你不能定義任何狀態,這導致代碼很差。一般有兩種類型的狀態:
一個全局變量:一般應該盡量避免全局變量。我遇到過的確需要在一個PL/SQL包或體中定義全局變量的合理需求。
一個全局常量:幾乎所有的重要產品系統都需要定義常量。如果你決定在你的系統中不允許定義常量,那么就會導致很差的代碼、多次重復定義相同的值,當需求改變的時候就會影響系統中不只一個地方,因此降低了可維護性。
如果你已經有一個包含了定義狀態的包的系統,那么這個解決方法會使得進行大量的重寫。在這種情況下,你需要決定是否值得這么做。
讓我們看下一個解決方法:
解決方法2: 將所有的包狀態移到另一個包里
這個解決方法的思想是將包體或包規范中的所有包狀態移到另一個包里,這個包作為“同伴狀態包”。這意味着我們降低了需要處理“ORA-06068”錯誤的次數,因為這些包本身並不存儲任何狀態,盡管它們因為各自的狀態而依賴於同伴包。在我的經歷中,在包體執行中發生的大多數改變——如果我們執行這個解決方法那就不會導致ORA-06068錯誤。如果我們重新編譯同伴狀態包,那仍然會發生ORA-06068錯誤。
讓我們看看這個解決方法的工作情況。
我們創建一個新的包叫做const ,如下所示,我們將我們之前定義的常量移到我們的包pkg的包體中。
create or replace package const as g_constant constant number := 1; end const; / show errors;
包pkg的包規范沒有改變,並且為了你的方便,下面重復一下:
create or replace package pkg as procedure p; end pkg; / show errors;
這個包體改變了,以便它之中不再有常量定義(它移到了包const中),而且現在插入語句使用包const 中定義的常量以獲得這個值。因此包pkg依賴於包const以獲得由常量g_constant定義的它的狀態:
create or replace package body pkg as procedure p is begin insert into t(x) values (const.g_constant); end p; end pkg; / show errors;
假設我們改變了對包pkg的包規范並在我們的系統中安裝了一個新的包const。現在我們登錄到我們的會話1中並執行這個存儲過程——它如意料般地執行成功:
session 1>exec pkg.p PL/SQL procedure successfully completed.
我們登錄到會話2中並重新編譯包pkg的這個包規范和包體:
session 2>@pkg_spec session 2>create or replace package pkg as procedure p; end pkg; / Package created. session 2>show errors; No errors. session 2>@pkg_body session 2>create or replace package body pkg as procedure p is begin insert into t(x) values (const.g_constant); end p; end pkg; / Package body created. session 2>show errors; No errors.
在會話1中,當我們重新執行這個存儲過程時,盡管我們重新編譯了這個包規定和包體,它仍然執行成功。這是因為這個包狀態是在包const中的(它已經被重新編譯了),並因此當我們重新編譯包pkg時包狀態沒有改變。.
當我們如下在會話2中重新編譯包const時會發生什么呢?:
session 2>@const session 2>create or replace package const as g_constant constant number := 1; end const; / Package created. session 2>show errors; No errors.
如果我們在會話1中重新執行包pkg,我們將如意料般得到ORA-04068錯誤。這個錯誤清楚地表明在包const中的包狀態改變了,並因此使得依賴於它的包pkg被無效化。
session 1>exec pkg.p BEGIN pkg.p; END; * ERROR at line 1: ORA-04068: existing state of packages has been discarded ORA-04061: existing state of package "ORA92.CONST" has been invalidated ORA-04065: not executed, altered or dropped package "ORA92.CONST" ORA-06508: PL/SQL: could not find program unit being called ORA-06512: at "ORA92.PKG", line 5 ORA-06512: at line 1
當然,如果我們之后在會話1中重新執行這個包,它看起來如預期般執行成功:
session 1>exec pkg.p PL/SQL procedure successfully completed.
解決方法2,盡管比解決方法1好些,但是具有以下缺陷:
它要求你總要將包的狀態移到包外,因此使得包狀態對於系統中的其它所有的包來說都是可見的。換句話說,你不能創建包私有的變量(或常量)(如果你在包體中聲明一個變量或常量,它不能被其它的包訪問到——它是定義它的包所私有的——這使得更好地封裝代碼)。這削弱了系統的封裝性,從而降低了系統的可維護性。事實上,如果我們這么做,我們應該只將常量作為任何包狀態的一部分(它是合理的並歡迎自我約束的)。
它要求你將包的所有狀態移到一個同伴狀態包里。這導致系統中同伴包的增大,所以這個解決方法不太好。如果你決定只有一個包具有所有其它包的狀態,那么你將遇到另一個問題——在中央包里,一個變量或常量的改變會導致系統中所有其它包無效——甚至包括那些與這個變量或常量無關的包。只有你能決定這兩個選擇(中央狀態包或每個包的同伴狀態包)哪個適合於你。
如果你已有系統具有定義了狀態的包,那么這個解決方法可能很難執行,因為它可能導致大量的重寫。這種情況下你需要衡量是每一次部署出現連續的ORA-04068錯誤,還是要一次性重寫系統。
我們下一組的解決方法是針對於前面提到的兩個解決方法的改進,但它們有較大的缺陷,以至於在這篇文章里使得解決方法1或解決方法2是最終的推薦解決方法。但是,我強烈建議你看看下面兩個解決方法來了解它們的機制,並基於你對系統的了解來作出判斷。
解決方法3: 監測ORA-0408錯誤並重新執行包的存儲過程
這個解決方法將處理錯誤的責任放到了客戶端。它的思想是Oracle 通過生成錯誤ORA-04068給客戶端提供了關於包狀態已經被無效化的信息和由客戶端來監測這個錯誤以及作出反應。客戶端可以選擇重新執行這個存儲過程,如果它需要的話。我們已經看到這個解決方法看起來是工作在SQL*Plus 中,當存儲過程的執行是在這個錯誤如意料般的發生之后。我們現在將看看在使用JDBC的Java程序中它的執行以及看看它是否管用。
首先讓我們回到在包pkg中有狀態的舊代碼。所以我們在包體中重新引進狀態,像以前一樣——這個代碼復制到下面以方便你查看:
create or replace package body pkg as g_constant constant number := 1; procedure p is begin insert into t(x) values (1); end p; end pkg; / show errors;
假設我們重新編譯了包體以便我們具有恰當的新代碼。我們將首先在一個Java程序中使用JDBC進行模擬一個會導致ORA-04068錯誤的環境。為此我們將:
使用JDBC在Java程序中獲得一個連接,
在Java程序中使用JDBC執行pkg.p 存儲過程,
在Java程序中休眠一段時間(10到20秒),
當我們的Java 程序休眠時,我們在一個單獨的SQL*Plus會話中重新編譯包pkg的包體,
在Java程序中使用JDBC重新執行pkg.p 存儲過程——這將導致ORA-04068錯誤。
叫做ExecutePackageProcedureTwice 的Java程序顯示如下。它執行了pkg.p存儲過程,休眠了20秒以給我們充足的時間來重新編譯這個包以模擬部署,然后重新執行這個存儲過程:
package dbj2ee.article2.design1; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.DriverManager; import oracle.jdbc.OracleDriver; public class ExecutePackageProcedureTwice { public static void main(String[] args) throws Exception { Connection conn = null; CallableStatement cstmt = null; long sleepInSecs = 20; try { conn = getConnection(); cstmt = conn.prepareCall("{call pkg.p()}"); executePkg(conn, cstmt); System.out.println("Sleeping for " + sleepInSecs + " seconds..."); Thread.sleep(sleepInSecs * 1000); System.out.println("Out of sleep..."); executePkg(conn, cstmt); } finally { try { if (cstmt != null) cstmt.close(); if (conn != null) conn.close(); } catch (Exception e) { e.printStackTrace(); } } } private static Connection getConnection() throws Exception { DriverManager.registerDriver(new OracleDriver()); return DriverManager.getConnection("jdbc:oracle:thin:@hercdev:1521:hercdev", "hercules", "hercules"); } private static void executePkg(Connection conn, CallableStatement cstmt) throws Exception { System.out.println("Executing the package..."); cstmt.executeUpdate(); conn.commit(); } }
現在讓我們再生ORA-04068錯誤。
設置恰當的CLASSPATH路徑,指向類的根路徑和classes12.jar(這個Jar包含Oracle JDBC執行),執行這個類,我們得到下面的結果:
M:\articles\dbj2ee\articles>java -cp "M:\classes12.jar;.;M:\learning\java\dbj2ee
\build\classes" dbj2ee.article2.design1.ExecutePackageProcedureTwice
Executing the package...
Sleeping for 20 seconds...
當Java程序運行到它開始休眠的地方時,我們在一個單獨的SQL*Plus會話中重新編譯這個包:
SQL> @pkg_body SQL> create or replace package body pkg as g_constant constant number := 1; procedure p is begin insert into t(x) values (1); end p; end pkg; / Package body created. SQL> show errors; No errors.
然后在Java程序結束休眠后,它如預期般地在試圖第二次執行這個包的時候丟出ORA-04068錯誤:
M:\articles\dbj2ee\articles>java -cp "M:\classes12.jar;.;M:\learning\java\dbj2ee \build\classes" dbj2ee.article2.design1.ExecutePackageProcedureTwice Executing the package... Sleeping for 20 seconds... Out of sleep... Executing the package... Exception in thread "main" java.sql.SQLException: ORA-04068: existing state of p ackages has been discarded ORA-04061: existing state of package body "ORA92.PKG" has been invalidated ORA-04065: not executed, altered or dropped package body "ORA92.PKG" ORA-06508: PL/SQL: could not find program unit being called ORA-06512: at line 1 at oracle.jdbc.dbaccess.DBError.throwSqlException(DBError.java:134) at oracle.jdbc.ttc7.TTIoer.processError(TTIoer.java:289) at oracle.jdbc.ttc7.Oall7.receive(Oall7.java:573) at oracle.jdbc.ttc7.TTC7Protocol.doOall7(TTC7Protocol.java:1891) at oracle.jdbc.ttc7.TTC7Protocol.executeFetch(TTC7Protocol.java:955) at oracle.jdbc.driver.OracleStatement.executeNonQuery(OracleStatement.ja va:2053) at oracle.jdbc.driver.OracleStatement.doExecuteOther(OracleStatement.jav a:1940) at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStateme nt.java:2709) at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePrepar edStatement.java:589) at dbj2ee.article2.design1.ExecutePackageProcedureTwice.executePkg(Execu tePackageProcedureTwice.java:38) at dbj2ee.article2.design1.ExecutePackageProcedureTwice.main(ExecutePack ageProcedureTwice.java:20)
現在,正如我們所說的,我們知道在客戶端級別(在這個例子中是在Java程序中)通過丟出的異常我們可以監測錯誤的代碼並通過重新執行這個包來作出響應。最簡單的執行如修改過的程序dbj2ee.article2.design2.ExecutePackageProcedureTwice 所顯示——與第一個版本的不同之處用粗體顯示,便於你查看。正如你所看到的,我們捕捉SQLException 並查看這個錯誤是否是ORA-04068——如果是,那我們重新執行這個包,否則我們再次拋出這個錯誤。
package dbj2ee.article2.design2; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import oracle.jdbc.OracleDriver; public class ExecutePackageProcedureTwice { public static void main(String[] args) throws Exception { Connection conn = null; CallableStatement cstmt = null; long sleepInSecs = 20; try { conn = getConnection(); cstmt = conn.prepareCall("{call pkg.p()}"); executePkg(conn, cstmt); System.out.println("Sleeping for " + sleepInSecs + " seconds..."); Thread.sleep(sleepInSecs * 1000); System.out.println("Out of sleep..."); executePkg(conn, cstmt); } catch (SQLException e) { if (reExecutionRequired(e)) { System.out.println("ORA-04068 detected - re-executing the package..."); executePkg(conn, cstmt); } else throw e; } finally { try { if (cstmt != null) cstmt.close(); if (conn != null) conn.close(); } catch (Exception e) { e.printStackTrace(); } } } private static boolean reExecutionRequired(SQLException e) { return "72000".equals(e.getSQLState()) && e.getErrorCode() == 4068; } private static Connection getConnection() throws Exception { DriverManager.registerDriver(new OracleDriver()); return DriverManager.getConnection("jdbc:oracle:thin:@hercdev:1521:hercdev", "hercules", "hercules"); } private static void executePkg(Connection conn, CallableStatement cstmt) throws Exception { System.out.println("Executing the package..."); cstmt.executeUpdate(); conn.commit(); } }
讓我們看看當我們執行這個程序並在另一個會話中編譯這個包的時候發生了什么。與前面一樣,我們執行這個程序並得到下面的輸出:
M:\articles\dbj2ee\articles>java -cp "M:\classes12.jar;.;M:\learning\java\dbj2ee \build\classes" dbj2ee.article2.design2.ExecutePackageProcedureTwice Executing the package... Sleeping for 20 seconds...
在另一個會話里,我們重新編譯這個包:
SQL> @pkg_body SQL> create or replace package body pkg as g_constant constant number := 1; procedure p is begin insert into t(x) values (1); end p; end pkg; / Package body created. SQL> show errors; No errors. SQL>
當我們回到我們的Java程序時,它輸出下面的信息作為重新執行這個包的一部分:
M:\articles\dbj2ee\articles>java -cp "M:\classes12.jar;.;M:\learning\java\dbj2ee \build\classes" dbj2ee.article2.design2.ExecutePackageProcedureTwice Executing the package... Sleeping for 20 seconds... Out of sleep... Executing the package... ORA-04068 detected - re-executing the package... Executing the package...
如你所看到的,我們監測這個錯誤並成功地重新執行這個包。
盡管這個解決方法看起來很好,但是它的缺陷很明顯:為了執行它,我們將在我們的Java程序中每次調用一個存儲過程時都需要捕捉這個異常。這個更改在大多數系統中都是被禁止的。這個解決方法還有另一個缺陷,我將在后面提到,這個缺陷使這個解決方法不能用於許多系統。
我們下一個潛在的解決方法精簡了這一節中展示的解決方法,使得包的重新執行對於一個已有的系統是透明的,因此使得它在這個系統中的執行是切實可行的。
解決方法4:透明地監測ORA-0408錯誤並重新執行包的存儲過程
這個解決方法的思想是:
我們用我們自己的封裝類,叫做MyConnectionWrapper,來替代Oracle的連接執行。做這個替代的最好地方是在驅動級別——通過編寫一個封裝驅動——盡管你可以在連接池執行級別做這個替代(例如在數據源中)。
我們的連接封裝會返回一個叫做CallableStatement的CallableStatement封裝,而不是無論何時我們調用它之上的方法prepareCall()就執行Oracle的CallableStatement。在其它所有的方法中,這個封裝類會用這個動作代替封裝的連接,因此它的行為和一個普通連接對象方式一樣。
我們的CallableStatement 封裝會在調用它上面的“execute”方法時捕捉異常——如果它監測到ORA-04068錯誤,它會透明地對這個封裝的CallableStatement對象重新執行這個方法。在其它所有的方法中,它會簡單地以封裝的CallableStatement 對象來代表它。
首先我們將執行我們自己的驅動,這個驅動執行java.sql.Driver接口並封裝Oracle驅動類。MyDriverWrapper類顯示如下:
package dbj2ee.article2.design3; import java.sql.Connection; import java.sql.Driver; import java.sql.DriverManager; import java.sql.DriverPropertyInfo; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.Properties; import java.util.logging.Logger; import oracle.jdbc.OracleDriver; public final class MyDriverWrapper implements Driver { private static final DriverPropertyInfo[] DRIVER_PROPERTY_INFO = new DriverPropertyInfo[0]; public static final String ACCEPTABLE_URL_PREFIX = "jdbc:dbj2ee:orawrapper:"; private static Driver driver = new OracleDriver(); static { try { DriverManager.registerDriver(new MyDriverWrapper()); } catch (Exception e) { e.printStackTrace(); } } public Connection connect(String url, Properties info) throws SQLException { String myUrl = url.replaceFirst(ACCEPTABLE_URL_PREFIX, "jdbc:oracle:thin:"); System.out.println("new url: " + myUrl); return new MyConnectionWrapper(driver.connect(myUrl, info)); } public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { return DRIVER_PROPERTY_INFO; } public boolean jdbcCompliant() { return true; } public boolean acceptsURL(String url) throws SQLException { return url != null && url.startsWith(ACCEPTABLE_URL_PREFIX); } public int getMinorVersion() { return 0; } public int getMajorVersion() { return 1; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { // TODO Auto-generated method stub return null; } }
注意這個類是怎樣定義它自己的私有前綴的——你可以設定任意的值。它還存儲了一個OracleDriver 對象的實例,它是做實際工作的。在連接方法中,驅動在URL中進行替代,它的具有Oracle thin driver前綴的私有前綴無縫地創建了一個適用於OracleDriver 的url。然后它通過指定OracleDriver實例獲得了Oracle連接。然后它封裝了這個連接和類MyConnectionWrapper(過會兒我們將看看這個類)並返回了MyConnectionWrapper對象。這就是我們透明替代我們自己的連接對象的方式。注意,你可以以多種方式來進行——例如,你可以在數據源級別替代這個連接,而不是在連接級別。
類MyConnectionWrapper 顯示如下。觀察下面關於這個類執行的信息:
它將一個連接對象作為構造器中的一個對象並將它存儲在一個私有的實例變量中。
它封裝了所有版本prepareCall()方法執行中的MyCallableStatement類的所有CallableStatement對象。
其它方法的執行簡單地用封裝連接中相應的方法來代表它們的動作。
package dbj2ee.article2.design3; import java.sql.Array; import java.sql.Blob; import java.sql.CallableStatement; import java.sql.Clob; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.NClob; import java.sql.PreparedStatement; import java.sql.SQLClientInfoException; import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.SQLXML; import java.sql.Savepoint; import java.sql.Statement; import java.sql.Struct; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executor; public class MyConnectionWrapper implements Connection { private Connection connection; public MyConnectionWrapper(Connection connection) { this.connection = connection; } public CallableStatement prepareCall(String sql) throws SQLException { return new MyCallableStatementWrapper(connection.prepareCall(sql)); } public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return connection.prepareCall(sql, resultSetType, resultSetConcurrency); } public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); } public void clearWarnings() throws SQLException { connection.clearWarnings(); } // ....... all other methods are simple delegation to the connection // instance variable and are not being shown to conserve space. @Override public <T> T unwrap(Class<T> iface) throws SQLException { // TODO Auto-generated method stub return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { // TODO Auto-generated method stub return false; } @Override public Statement createStatement() throws SQLException { // TODO Auto-generated method stub return null; } @Override public PreparedStatement prepareStatement(String sql) throws SQLException { // TODO Auto-generated method stub return null; } @Override public String nativeSQL(String sql) throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setAutoCommit(boolean autoCommit) throws SQLException { // TODO Auto-generated method stub } @Override public boolean getAutoCommit() throws SQLException { // TODO Auto-generated method stub return false; } @Override public void commit() throws SQLException { // TODO Auto-generated method stub } @Override public void rollback() throws SQLException { // TODO Auto-generated method stub } @Override public void close() throws SQLException { // TODO Auto-generated method stub } @Override public boolean isClosed() throws SQLException { // TODO Auto-generated method stub return false; } @Override public DatabaseMetaData getMetaData() throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setReadOnly(boolean readOnly) throws SQLException { // TODO Auto-generated method stub } @Override public boolean isReadOnly() throws SQLException { // TODO Auto-generated method stub return false; } @Override public void setCatalog(String catalog) throws SQLException { // TODO Auto-generated method stub } @Override public String getCatalog() throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setTransactionIsolation(int level) throws SQLException { // TODO Auto-generated method stub } @Override public int getTransactionIsolation() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public SQLWarning getWarnings() throws SQLException { // TODO Auto-generated method stub return null; } @Override public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { // TODO Auto-generated method stub return null; } @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Map<String, Class<?>> getTypeMap() throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setTypeMap(Map<String, Class<?>> map) throws SQLException { // TODO Auto-generated method stub } @Override public void setHoldability(int holdability) throws SQLException { // TODO Auto-generated method stub } @Override public int getHoldability() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public Savepoint setSavepoint() throws SQLException { // TODO Auto-generated method stub return null; } @Override public Savepoint setSavepoint(String name) throws SQLException { // TODO Auto-generated method stub return null; } @Override public void rollback(Savepoint savepoint) throws SQLException { // TODO Auto-generated method stub } @Override public void releaseSavepoint(Savepoint savepoint) throws SQLException { // TODO Auto-generated method stub } @Override public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { // TODO Auto-generated method stub return null; } @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { // TODO Auto-generated method stub return null; } @Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { // TODO Auto-generated method stub return null; } @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { // TODO Auto-generated method stub return null; } @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Clob createClob() throws SQLException { // TODO Auto-generated method stub return null; } @Override public Blob createBlob() throws SQLException { // TODO Auto-generated method stub return null; } @Override public NClob createNClob() throws SQLException { // TODO Auto-generated method stub return null; } @Override public SQLXML createSQLXML() throws SQLException { // TODO Auto-generated method stub return null; } @Override public boolean isValid(int timeout) throws SQLException { // TODO Auto-generated method stub return false; } @Override public void setClientInfo(String name, String value) throws SQLClientInfoException { // TODO Auto-generated method stub } @Override public void setClientInfo(Properties properties) throws SQLClientInfoException { // TODO Auto-generated method stub } @Override public String getClientInfo(String name) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Properties getClientInfo() throws SQLException { // TODO Auto-generated method stub return null; } @Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Struct createStruct(String typeName, Object[] attributes) throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setSchema(String schema) throws SQLException { // TODO Auto-generated method stub } @Override public String getSchema() throws SQLException { // TODO Auto-generated method stub return null; } @Override public void abort(Executor executor) throws SQLException { // TODO Auto-generated method stub } @Override public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { // TODO Auto-generated method stub } @Override public int getNetworkTimeout() throws SQLException { // TODO Auto-generated method stub return 0; } }
對於CallableStatement封裝類的執行,它的父接口PreparedStatement和Statement的執行是必要的。因此我們創建三個封裝對象——MyStatementWrapper封裝了Statement對象;MyPreparedStatementWrapper封裝了PreparedStatement對象,而MyCallableStatementWrapper封裝了CallableStatement對象。
MyStatementWrapper 類是一個簡單的封裝類,它封裝了Statement對象,下面顯示了它的一部分——這個代碼很容易理解:
package dbj2ee.article2.design3; import java.sql.Connection; import java.sql.Statement; import java.sql.SQLWarning; import java.sql.SQLException; import java.sql.ResultSet; public class MyStatementWrapper implements Statement { Statement statement; public MyStatementWrapper(Statement statement) { this.statement = statement; } public void addBatch(String sql) throws SQLException { statement.addBatch(sql); } // ....... all other methods are simple delegation to the connection // instance variable and are not being shown to conserve space. @Override public <T> T unwrap(Class<T> iface) throws SQLException { // TODO Auto-generated method stub return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { // TODO Auto-generated method stub return false; } @Override public ResultSet executeQuery(String sql) throws SQLException { // TODO Auto-generated method stub return null; } @Override public int executeUpdate(String sql) throws SQLException { // TODO Auto-generated method stub return 0; } @Override public void close() throws SQLException { // TODO Auto-generated method stub } @Override public int getMaxFieldSize() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public void setMaxFieldSize(int max) throws SQLException { // TODO Auto-generated method stub } @Override public int getMaxRows() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public void setMaxRows(int max) throws SQLException { // TODO Auto-generated method stub } @Override public void setEscapeProcessing(boolean enable) throws SQLException { // TODO Auto-generated method stub } @Override public int getQueryTimeout() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public void setQueryTimeout(int seconds) throws SQLException { // TODO Auto-generated method stub } @Override public void cancel() throws SQLException { // TODO Auto-generated method stub } @Override public SQLWarning getWarnings() throws SQLException { // TODO Auto-generated method stub return null; } @Override public void clearWarnings() throws SQLException { // TODO Auto-generated method stub } @Override public void setCursorName(String name) throws SQLException { // TODO Auto-generated method stub } @Override public boolean execute(String sql) throws SQLException { // TODO Auto-generated method stub return false; } @Override public ResultSet getResultSet() throws SQLException { // TODO Auto-generated method stub return null; } @Override public int getUpdateCount() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public boolean getMoreResults() throws SQLException { // TODO Auto-generated method stub return false; } @Override public void setFetchDirection(int direction) throws SQLException { // TODO Auto-generated method stub } @Override public int getFetchDirection() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public void setFetchSize(int rows) throws SQLException { // TODO Auto-generated method stub } @Override public int getFetchSize() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public int getResultSetConcurrency() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public int getResultSetType() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public void clearBatch() throws SQLException { // TODO Auto-generated method stub } @Override public int[] executeBatch() throws SQLException { // TODO Auto-generated method stub return null; } @Override public Connection getConnection() throws SQLException { // TODO Auto-generated method stub return null; } @Override public boolean getMoreResults(int current) throws SQLException { // TODO Auto-generated method stub return false; } @Override public ResultSet getGeneratedKeys() throws SQLException { // TODO Auto-generated method stub return null; } @Override public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException { // TODO Auto-generated method stub return 0; } @Override public int executeUpdate(String sql, int[] columnIndexes) throws SQLException { // TODO Auto-generated method stub return 0; } @Override public int executeUpdate(String sql, String[] columnNames) throws SQLException { // TODO Auto-generated method stub return 0; } @Override public boolean execute(String sql, int autoGeneratedKeys) throws SQLException { // TODO Auto-generated method stub return false; } @Override public boolean execute(String sql, int[] columnIndexes) throws SQLException { // TODO Auto-generated method stub return false; } @Override public boolean execute(String sql, String[] columnNames) throws SQLException { // TODO Auto-generated method stub return false; } @Override public int getResultSetHoldability() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public boolean isClosed() throws SQLException { // TODO Auto-generated method stub return false; } @Override public void setPoolable(boolean poolable) throws SQLException { // TODO Auto-generated method stub } @Override public boolean isPoolable() throws SQLException { // TODO Auto-generated method stub return false; } @Override public void closeOnCompletion() throws SQLException { // TODO Auto-generated method stub } @Override public boolean isCloseOnCompletion() throws SQLException { // TODO Auto-generated method stub return false; } }
MyPreparedStatementWrapper類是一個簡單封裝類,它封裝了PreparedStatement對象,下面顯示了它的一部分——這個代碼很容易理解:
package dbj2ee.article2.design3; import java.net.URL; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.SQLXML; import java.sql.ResultSet; import java.sql.Blob; import java.sql.Clob; import java.sql.ResultSetMetaData; import java.sql.RowId; import java.sql.Array; import java.io.InputStream; import java.math.BigDecimal; import java.io.Reader; import java.sql.Date; import java.sql.NClob; import java.sql.ParameterMetaData; import java.util.Calendar; import java.sql.Ref; import java.sql.Time; import java.sql.Timestamp; public class MyPreparedStatementWrapper extends MyStatementWrapper implements PreparedStatement { private PreparedStatement preparedStatement; public MyPreparedStatementWrapper(PreparedStatement preparedStatement) { super(preparedStatement); this.preparedStatement = preparedStatement; } public ParameterMetaData getParameterMetaData() throws SQLException { return preparedStatement.getParameterMetaData(); } // ....... all other methods are simple delegation to the connection // instance variable and are not being shown to conserve space. @Override public ResultSet executeQuery() throws SQLException { // TODO Auto-generated method stub return null; } @Override public int executeUpdate() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public void setNull(int parameterIndex, int sqlType) throws SQLException { // TODO Auto-generated method stub } @Override public void setBoolean(int parameterIndex, boolean x) throws SQLException { // TODO Auto-generated method stub } @Override public void setByte(int parameterIndex, byte x) throws SQLException { // TODO Auto-generated method stub } @Override public void setShort(int parameterIndex, short x) throws SQLException { // TODO Auto-generated method stub } @Override public void setInt(int parameterIndex, int x) throws SQLException { // TODO Auto-generated method stub } @Override public void setLong(int parameterIndex, long x) throws SQLException { // TODO Auto-generated method stub } @Override public void setFloat(int parameterIndex, float x) throws SQLException { // TODO Auto-generated method stub } @Override public void setDouble(int parameterIndex, double x) throws SQLException { // TODO Auto-generated method stub } @Override public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { // TODO Auto-generated method stub } @Override public void setString(int parameterIndex, String x) throws SQLException { // TODO Auto-generated method stub } @Override public void setBytes(int parameterIndex, byte[] x) throws SQLException { // TODO Auto-generated method stub } @Override public void setDate(int parameterIndex, Date x) throws SQLException { // TODO Auto-generated method stub } @Override public void setTime(int parameterIndex, Time x) throws SQLException { // TODO Auto-generated method stub } @Override public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { // TODO Auto-generated method stub } @Override public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { // TODO Auto-generated method stub } @Override public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { // TODO Auto-generated method stub } @Override public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { // TODO Auto-generated method stub } @Override public void clearParameters() throws SQLException { // TODO Auto-generated method stub } @Override public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException { // TODO Auto-generated method stub } @Override public void setObject(int parameterIndex, Object x) throws SQLException { // TODO Auto-generated method stub } @Override public boolean execute() throws SQLException { // TODO Auto-generated method stub return false; } @Override public void addBatch() throws SQLException { // TODO Auto-generated method stub } @Override public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { // TODO Auto-generated method stub } @Override public void setRef(int parameterIndex, Ref x) throws SQLException { // TODO Auto-generated method stub } @Override public void setBlob(int parameterIndex, Blob x) throws SQLException { // TODO Auto-generated method stub } @Override public void setClob(int parameterIndex, Clob x) throws SQLException { // TODO Auto-generated method stub } @Override public void setArray(int parameterIndex, Array x) throws SQLException { // TODO Auto-generated method stub } @Override public ResultSetMetaData getMetaData() throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { // TODO Auto-generated method stub } @Override public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { // TODO Auto-generated method stub } @Override public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { // TODO Auto-generated method stub } @Override public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { // TODO Auto-generated method stub } @Override public void setURL(int parameterIndex, URL x) throws SQLException { // TODO Auto-generated method stub } @Override public void setRowId(int parameterIndex, RowId x) throws SQLException { // TODO Auto-generated method stub } @Override public void setNString(int parameterIndex, String value) throws SQLException { // TODO Auto-generated method stub } @Override public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setNClob(int parameterIndex, NClob value) throws SQLException { // TODO Auto-generated method stub } @Override public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { // TODO Auto-generated method stub } @Override public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException { // TODO Auto-generated method stub } @Override public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { // TODO Auto-generated method stub } @Override public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { // TODO Auto-generated method stub } @Override public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { // TODO Auto-generated method stub } @Override public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { // TODO Auto-generated method stub } @Override public void setClob(int parameterIndex, Reader reader) throws SQLException { // TODO Auto-generated method stub } @Override public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { // TODO Auto-generated method stub } @Override public void setNClob(int parameterIndex, Reader reader) throws SQLException { // TODO Auto-generated method stub } }
MyCallableStatementWrapper 類顯示如下。觀察下面關於這個類的執行信息:
它擴展了MyPreparedStatementWrapper類。
像其它的封裝類一樣,它存儲一個CallableStatement對象作為它的實例變量的一部分。
對於所有執行存儲過程的方法來說,如果它透明地檢測到ORA-04068錯誤,它就會覆蓋這個執行,重新調用這個方法。注意,事實上,你可能還需要以類似的方式覆蓋其它的一些從PreparedStatement 繼承而來的方法。
其它方法的執行簡單地以封裝對象CallableStatement中的相關方法來代表它們的動作。
package dbj2ee.article2.design3; import java.io.InputStream; import java.io.Reader; import java.util.Map; import java.sql.CallableStatement; import java.sql.SQLException; import java.sql.SQLXML; import java.sql.Blob; import java.sql.Clob; import java.sql.Array; import java.math.BigDecimal; import java.net.URL; import java.sql.Date; import java.sql.NClob; import java.util.Calendar; import java.sql.Ref; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.RowId; import java.sql.Time; import java.sql.Timestamp; public class MyCallableStatementWrapper extends MyPreparedStatementWrapper implements CallableStatement { private CallableStatement callableStatement; public MyCallableStatementWrapper(CallableStatement statement) { super(statement); this.callableStatement = (CallableStatement) statement; } public boolean execute() throws SQLException { boolean result = true; try { result = callableStatement.execute(); } catch (SQLException e) { System.out.println("code:" + e.getErrorCode() + ", sql state: " + e.getSQLState()); if (reExecutionRequired(e)) { System.out.println("re-executing package "); result = callableStatement.execute(); } else throw e; } return result; } public int executeUpdate() throws SQLException { int result = 0; try { result = callableStatement.executeUpdate(); } catch (SQLException e) { System.out.println("code:" + e.getErrorCode() + ", sql state: " + e.getSQLState()); if (reExecutionRequired(e)) { System.out.println("re-executing package "); result = callableStatement.executeUpdate(); } else throw e; } return result; } private boolean reExecutionRequired(SQLException e) { return "72000".equals(e.getSQLState()) && e.getErrorCode() == 4068; } public URL getURL(int parameterIndex) throws SQLException { return callableStatement.getURL(parameterIndex); } // ....... all other methods are simple delegation to the connection // instance variable and are not being shown to conserve space. @Override public ResultSet executeQuery() throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setNull(int parameterIndex, int sqlType) throws SQLException { // TODO Auto-generated method stub } @Override public void setBoolean(int parameterIndex, boolean x) throws SQLException { // TODO Auto-generated method stub } @Override public void setByte(int parameterIndex, byte x) throws SQLException { // TODO Auto-generated method stub } @Override public void setShort(int parameterIndex, short x) throws SQLException { // TODO Auto-generated method stub } @Override public void setInt(int parameterIndex, int x) throws SQLException { // TODO Auto-generated method stub } @Override public void setLong(int parameterIndex, long x) throws SQLException { // TODO Auto-generated method stub } @Override public void setFloat(int parameterIndex, float x) throws SQLException { // TODO Auto-generated method stub } @Override public void setDouble(int parameterIndex, double x) throws SQLException { // TODO Auto-generated method stub } @Override public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { // TODO Auto-generated method stub } @Override public void setString(int parameterIndex, String x) throws SQLException { // TODO Auto-generated method stub } @Override public void setBytes(int parameterIndex, byte[] x) throws SQLException { // TODO Auto-generated method stub } @Override public void setDate(int parameterIndex, Date x) throws SQLException { // TODO Auto-generated method stub } @Override public void setTime(int parameterIndex, Time x) throws SQLException { // TODO Auto-generated method stub } @Override public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { // TODO Auto-generated method stub } @Override public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { // TODO Auto-generated method stub } @Override public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { // TODO Auto-generated method stub } @Override public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { // TODO Auto-generated method stub } @Override public void clearParameters() throws SQLException { // TODO Auto-generated method stub } @Override public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException { // TODO Auto-generated method stub } @Override public void setObject(int parameterIndex, Object x) throws SQLException { // TODO Auto-generated method stub } @Override public void addBatch() throws SQLException { // TODO Auto-generated method stub } @Override public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { // TODO Auto-generated method stub } @Override public void setRef(int parameterIndex, Ref x) throws SQLException { // TODO Auto-generated method stub } @Override public void setBlob(int parameterIndex, Blob x) throws SQLException { // TODO Auto-generated method stub } @Override public void setClob(int parameterIndex, Clob x) throws SQLException { // TODO Auto-generated method stub } @Override public void setArray(int parameterIndex, Array x) throws SQLException { // TODO Auto-generated method stub } @Override public ResultSetMetaData getMetaData() throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { // TODO Auto-generated method stub } @Override public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { // TODO Auto-generated method stub } @Override public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { // TODO Auto-generated method stub } @Override public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { // TODO Auto-generated method stub } @Override public void setURL(int parameterIndex, URL x) throws SQLException { // TODO Auto-generated method stub } @Override public void setRowId(int parameterIndex, RowId x) throws SQLException { // TODO Auto-generated method stub } @Override public void setNString(int parameterIndex, String value) throws SQLException { // TODO Auto-generated method stub } @Override public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setNClob(int parameterIndex, NClob value) throws SQLException { // TODO Auto-generated method stub } @Override public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { // TODO Auto-generated method stub } @Override public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException { // TODO Auto-generated method stub } @Override public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { // TODO Auto-generated method stub } @Override public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { // TODO Auto-generated method stub } @Override public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { // TODO Auto-generated method stub } @Override public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { // TODO Auto-generated method stub } @Override public void setClob(int parameterIndex, Reader reader) throws SQLException { // TODO Auto-generated method stub } @Override public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { // TODO Auto-generated method stub } @Override public void setNClob(int parameterIndex, Reader reader) throws SQLException { // TODO Auto-generated method stub } @Override public void registerOutParameter(int parameterIndex, int sqlType) throws SQLException { // TODO Auto-generated method stub } @Override public void registerOutParameter(int parameterIndex, int sqlType, int scale) throws SQLException { // TODO Auto-generated method stub } @Override public boolean wasNull() throws SQLException { // TODO Auto-generated method stub return false; } @Override public String getString(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return null; } @Override public boolean getBoolean(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return false; } @Override public byte getByte(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return 0; } @Override public short getShort(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return 0; } @Override public int getInt(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return 0; } @Override public long getLong(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return 0; } @Override public float getFloat(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return 0; } @Override public double getDouble(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return 0; } @Override public BigDecimal getBigDecimal(int parameterIndex, int scale) throws SQLException { // TODO Auto-generated method stub return null; } @Override public byte[] getBytes(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Date getDate(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Time getTime(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Timestamp getTimestamp(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Object getObject(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return null; } @Override public BigDecimal getBigDecimal(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Object getObject(int parameterIndex, Map<String, Class<?>> map) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Ref getRef(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Blob getBlob(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Clob getClob(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Array getArray(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Date getDate(int parameterIndex, Calendar cal) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Time getTime(int parameterIndex, Calendar cal) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Timestamp getTimestamp(int parameterIndex, Calendar cal) throws SQLException { // TODO Auto-generated method stub return null; } @Override public void registerOutParameter(int parameterIndex, int sqlType, String typeName) throws SQLException { // TODO Auto-generated method stub } @Override public void registerOutParameter(String parameterName, int sqlType) throws SQLException { // TODO Auto-generated method stub } @Override public void registerOutParameter(String parameterName, int sqlType, int scale) throws SQLException { // TODO Auto-generated method stub } @Override public void registerOutParameter(String parameterName, int sqlType, String typeName) throws SQLException { // TODO Auto-generated method stub } @Override public void setURL(String parameterName, URL val) throws SQLException { // TODO Auto-generated method stub } @Override public void setNull(String parameterName, int sqlType) throws SQLException { // TODO Auto-generated method stub } @Override public void setBoolean(String parameterName, boolean x) throws SQLException { // TODO Auto-generated method stub } @Override public void setByte(String parameterName, byte x) throws SQLException { // TODO Auto-generated method stub } @Override public void setShort(String parameterName, short x) throws SQLException { // TODO Auto-generated method stub } @Override public void setInt(String parameterName, int x) throws SQLException { // TODO Auto-generated method stub } @Override public void setLong(String parameterName, long x) throws SQLException { // TODO Auto-generated method stub } @Override public void setFloat(String parameterName, float x) throws SQLException { // TODO Auto-generated method stub } @Override public void setDouble(String parameterName, double x) throws SQLException { // TODO Auto-generated method stub } @Override public void setBigDecimal(String parameterName, BigDecimal x) throws SQLException { // TODO Auto-generated method stub } @Override public void setString(String parameterName, String x) throws SQLException { // TODO Auto-generated method stub } @Override public void setBytes(String parameterName, byte[] x) throws SQLException { // TODO Auto-generated method stub } @Override public void setDate(String parameterName, Date x) throws SQLException { // TODO Auto-generated method stub } @Override public void setTime(String parameterName, Time x) throws SQLException { // TODO Auto-generated method stub } @Override public void setTimestamp(String parameterName, Timestamp x) throws SQLException { // TODO Auto-generated method stub } @Override public void setAsciiStream(String parameterName, InputStream x, int length) throws SQLException { // TODO Auto-generated method stub } @Override public void setBinaryStream(String parameterName, InputStream x, int length) throws SQLException { // TODO Auto-generated method stub } @Override public void setObject(String parameterName, Object x, int targetSqlType, int scale) throws SQLException { // TODO Auto-generated method stub } @Override public void setObject(String parameterName, Object x, int targetSqlType) throws SQLException { // TODO Auto-generated method stub } @Override public void setObject(String parameterName, Object x) throws SQLException { // TODO Auto-generated method stub } @Override public void setCharacterStream(String parameterName, Reader reader, int length) throws SQLException { // TODO Auto-generated method stub } @Override public void setDate(String parameterName, Date x, Calendar cal) throws SQLException { // TODO Auto-generated method stub } @Override public void setTime(String parameterName, Time x, Calendar cal) throws SQLException { // TODO Auto-generated method stub } @Override public void setTimestamp(String parameterName, Timestamp x, Calendar cal) throws SQLException { // TODO Auto-generated method stub } @Override public void setNull(String parameterName, int sqlType, String typeName) throws SQLException { // TODO Auto-generated method stub } @Override public String getString(String parameterName) throws SQLException { // TODO Auto-generated method stub return null; } @Override public boolean getBoolean(String parameterName) throws SQLException { // TODO Auto-generated method stub return false; } @Override public byte getByte(String parameterName) throws SQLException { // TODO Auto-generated method stub return 0; } @Override public short getShort(String parameterName) throws SQLException { // TODO Auto-generated method stub return 0; } @Override public int getInt(String parameterName) throws SQLException { // TODO Auto-generated method stub return 0; } @Override public long getLong(String parameterName) throws SQLException { // TODO Auto-generated method stub return 0; } @Override public float getFloat(String parameterName) throws SQLException { // TODO Auto-generated method stub return 0; } @Override public double getDouble(String parameterName) throws SQLException { // TODO Auto-generated method stub return 0; } @Override public byte[] getBytes(String parameterName) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Date getDate(String parameterName) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Time getTime(String parameterName) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Timestamp getTimestamp(String parameterName) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Object getObject(String parameterName) throws SQLException { // TODO Auto-generated method stub return null; } @Override public BigDecimal getBigDecimal(String parameterName) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Object getObject(String parameterName, Map<String, Class<?>> map) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Ref getRef(String parameterName) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Blob getBlob(String parameterName) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Clob getClob(String parameterName) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Array getArray(String parameterName) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Date getDate(String parameterName, Calendar cal) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Time getTime(String parameterName, Calendar cal) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Timestamp getTimestamp(String parameterName, Calendar cal) throws SQLException { // TODO Auto-generated method stub return null; } @Override public URL getURL(String parameterName) throws SQLException { // TODO Auto-generated method stub return null; } @Override public RowId getRowId(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return null; } @Override public RowId getRowId(String parameterName) throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setRowId(String parameterName, RowId x) throws SQLException { // TODO Auto-generated method stub } @Override public void setNString(String parameterName, String value) throws SQLException { // TODO Auto-generated method stub } @Override public void setNCharacterStream(String parameterName, Reader value, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setNClob(String parameterName, NClob value) throws SQLException { // TODO Auto-generated method stub } @Override public void setClob(String parameterName, Reader reader, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setBlob(String parameterName, InputStream inputStream, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setNClob(String parameterName, Reader reader, long length) throws SQLException { // TODO Auto-generated method stub } @Override public NClob getNClob(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return null; } @Override public NClob getNClob(String parameterName) throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setSQLXML(String parameterName, SQLXML xmlObject) throws SQLException { // TODO Auto-generated method stub } @Override public SQLXML getSQLXML(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return null; } @Override public SQLXML getSQLXML(String parameterName) throws SQLException { // TODO Auto-generated method stub return null; } @Override public String getNString(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return null; } @Override public String getNString(String parameterName) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Reader getNCharacterStream(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Reader getNCharacterStream(String parameterName) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Reader getCharacterStream(int parameterIndex) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Reader getCharacterStream(String parameterName) throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setBlob(String parameterName, Blob x) throws SQLException { // TODO Auto-generated method stub } @Override public void setClob(String parameterName, Clob x) throws SQLException { // TODO Auto-generated method stub } @Override public void setAsciiStream(String parameterName, InputStream x, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setBinaryStream(String parameterName, InputStream x, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setCharacterStream(String parameterName, Reader reader, long length) throws SQLException { // TODO Auto-generated method stub } @Override public void setAsciiStream(String parameterName, InputStream x) throws SQLException { // TODO Auto-generated method stub } @Override public void setBinaryStream(String parameterName, InputStream x) throws SQLException { // TODO Auto-generated method stub } @Override public void setCharacterStream(String parameterName, Reader reader) throws SQLException { // TODO Auto-generated method stub } @Override public void setNCharacterStream(String parameterName, Reader value) throws SQLException { // TODO Auto-generated method stub } @Override public void setClob(String parameterName, Reader reader) throws SQLException { // TODO Auto-generated method stub } @Override public void setBlob(String parameterName, InputStream inputStream) throws SQLException { // TODO Auto-generated method stub } @Override public void setNClob(String parameterName, Reader reader) throws SQLException { // TODO Auto-generated method stub } @Override public <T> T getObject(int parameterIndex, Class<T> type) throws SQLException { // TODO Auto-generated method stub return null; } @Override public <T> T getObject(String parameterName, Class<T> type) throws SQLException { // TODO Auto-generated method stub return null; } }
最后,我們可以看看我們的使用了這個解決方法的ExecutePackageProcedureTwice 類。它顯示如下。它和這一節開頭的ExecutePackageProcedureTwice 非常類似——除了以下不同(在類的清單中以粗體顯示):
它打印出連接和可調用的聲明類來顯示這些類確實存在於我們的封裝類中。
獲得連接的代碼首先確定我們的驅動類通過使用Class.forName()加載進來了。然后使用我們私有的前綴而不是“oracle:jdbc:thin:”前綴,以便當獲取連接時我們的驅動會被DriverManager 選擇到,從而使得所有相關的JDBC類都被替換為我們的封裝類。
package dbj2ee.article2.design3; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.DriverManager; public class ExecutePackageProcedureTwice { public static void main(String[] args) throws Exception { Connection conn = null; CallableStatement cstmt = null; long sleepInSecs = 20; try { conn = getConnection(); System.out.println("connection class: " + conn.getClass()); cstmt = conn.prepareCall("{call pkg.p()}"); executePkg(conn, cstmt); System.out.println("Sleeping for " + sleepInSecs + " seconds..."); Thread.sleep(sleepInSecs * 1000); System.out.println("Out of sleep..."); executePkg(conn, cstmt); } finally { try { if (cstmt != null) cstmt.close(); if (conn != null) conn.close(); } catch (Exception e) { e.printStackTrace(); } } } private static Connection getConnection() throws Exception { Class.forName("dbj2ee.article2.design3.MyDriverWrapper"); return DriverManager.getConnection(MyDriverWrapper.ACCEPTABLE_URL_PREFIX + "rmenon/rmenon@devhost:1521:ora92"); } private static void executePkg(Connection conn, CallableStatement cstmt) throws Exception { System.out.println("Executing the package..."); cstmt.executeUpdate(); conn.commit(); } }
當我們執行這個類的時候,我們得到下面的結果(注意連接類和可調用的聲明類指向我們的封裝類):
M:\articles\dbj2ee\articles>java -cp "M:\classes12.jar;.;M:\learning\java\dbj2ee \build\classes" dbj2ee.article2.design3.ExecutePackageProcedureTwice new url: jdbc:oracle:thin:rmenon/rmenon@devhost:1521:ora92 connection class: class dbj2ee.article2.design3.MyConnectionWrapper callable statement class: class dbj2ee.article2.design3.MyCallableStatementWrapper Executing the package... Sleeping for 20 seconds...
然后我在另一個會話中像以前一樣重新編譯這個包:
SQL> @pkg_body SQL> create or replace package body pkg as g_constant constant number := 1; procedure p is begin insert into t(x) values (1); end p; end pkg; / Package body created. SQL> show errors; No errors.
當我們回頭觀察我們的Java執行時,我們會在程序成功地重新執行這個包后看到下面的內容:
M:\articles\dbj2ee\articles>java -cp "M:\classes12.jar;.;M:\learning\java\dbj2ee \build\classes" dbj2ee.article2.design3.ExecutePackageProcedureTwice new url: jdbc:oracle:thin:rmenon/rmenon@devhost:1521:ora92 connection class: class dbj2ee.article2.design3.MyConnectionWrapper callable statement class: class dbj2ee.article2.design3.MyCallableStatementWrapper Executing the package... Sleeping for 20 seconds... Out of sleep... Executing the package... code:4068, sql state: 72000 re-executing package
注意,如果你使用一個連接池那么你可以指定連接池里正確的驅動使用相同的技術。
因此我們可以設計這樣一個解決方法,它看起來對於幾乎所有的情況都是透明的。我曾經以為這是最佳的解決方法。但是后來發現在這個看似最佳的解決方法中有些問題。現在我描述一下。
考慮下面的場景:
你有一個包pkg ,它依賴於包const 中的一個常量。包pkg有兩個方法method1和method2,它們都依賴於常量const1——它的值設為1。
你從連接池得到一個連接。
在你的Java代碼中,你執行方法pkg.method1——它使用常量的值,而這個常量的值現在是1。
現在,作為部署的一部分,編譯包const ——常量的值變為了2。
你的事務執行下一步——調用方法pkg.method2。
因為你已經執行了在這一節中提到的“默默的重新執行技術”, method2 會默默地忽略ORA-04068並獲得常量的新值,現在是2。
問題是這可能導致事務中結果不一致。這是因為你違反了這個假定——在包(或一般是包狀態)中定義的常量應該在一個給定會話中始終保持為一個相同值——否則就不能保證你依靠事務的語法得到一致的結果。
因此這個解決方法在包的存儲過程重新執行后不能給出正確結果的所有情況下是不可行的。這是可能發生的,比如舉例來說,你的包的存儲過程現在的執行依賴於先前包的狀態。這種情況是比較常見的。
總結和推薦的解決方法
我們看了這篇文章中對於ORA-04068錯誤的多個解決方法,並對於每一個解決方法都做了大量的評測。下面是我基於各個場景做出的建議:
我建議不論在什么情況下,都盡可能地不要在包規范或包體中使用 全局變量。
最簡單的解決方法是使用無狀態的包(我們的解決方法1),如果可以這樣,那么這就是我所推薦的。你應該努力使你的包無狀態化。
第二個最佳解決方法(大多數情況下可行的方法)是為每一個包狀態為獨立的包添加同伴包。這確保了只有當你真的改變了同伴狀態包的時候才會遇到ORA-04068錯誤——這種情況應該相對很少見——特別是當狀態只包括常量的時候。如果你不想有同伴包,那么你可以有一個中央包包括系統中所有的常量——這會導致比一般更多的ORA-04048錯誤——但是注意,即使你只改變了一個包的狀態,你也需要刷出你的連接池,所以這不像它聽起來的那么差。
我不推薦解決方法4(或解決方法3),因為我發現很難擔保它們可以在任何復雜的系統中起作用。然而它們很少會失敗,這個解決方法就像一個定時炸彈一樣准備好在這些很少見的環境中爆炸。
