<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    小菜毛毛技術(shù)分享

    與大家共同成長(zhǎng)

      BlogJava :: 首頁(yè) :: 聯(lián)系 :: 聚合  :: 管理
      164 Posts :: 141 Stories :: 94 Comments :: 0 Trackbacks
    轉(zhuǎn)載自:http://database.ctocio.com.cn/tips/14/8106014.shtml
    在Oracle 里,如果你想編寫(xiě)存儲(chǔ)過(guò)程你當(dāng)然應(yīng)該使用PL/SQL包。在這篇文章里,假設(shè)你一般了解PL/SQL 和非常熟悉PL/SQL 包。這篇文章關(guān)注于一個(gè)令人討厭的錯(cuò)誤,這個(gè)錯(cuò)誤使許多使用PL/SQL以及使用API(例如JDBC)從應(yīng)用層調(diào)用它的開(kāi)發(fā)人員很苦惱。

      【IT專(zhuān)家網(wǎng)獨(dú)家】

      導(dǎo)言

      在我們需要與數(shù)據(jù)庫(kù)進(jìn)行交互時(shí),應(yīng)盡可能地使用存儲(chǔ)過(guò)程——無(wú)論我們使用哪個(gè)數(shù)據(jù)庫(kù)。這是假設(shè)這個(gè)數(shù)據(jù)庫(kù)提供了編寫(xiě)存儲(chǔ)過(guò)程的工具,大多數(shù)主要的數(shù)據(jù)庫(kù)都確實(shí)如此,例如Oracle、MySQL和SQL Server。而且無(wú)論你是使用Java、.NET或任何其它的編程語(yǔ)言或框架。

      在Oracle 里,如果你想編寫(xiě)存儲(chǔ)過(guò)程你當(dāng)然應(yīng)該使用PL/SQL包。在這篇文章里,假設(shè)你一般了解PL/SQL 和非常熟悉PL/SQL 包。這篇文章關(guān)注于一個(gè)令人討厭的錯(cuò)誤,這個(gè)錯(cuò)誤使許多使用PL/SQL以及使用API(例如JDBC)從應(yīng)用層調(diào)用它的開(kāi)發(fā)人員很苦惱。這個(gè)錯(cuò)誤就是“ORA-04068: existing state of packages has been discarded”。這個(gè)錯(cuò)誤是當(dāng)Oracle認(rèn)為你的包狀態(tài)出于某種原因是無(wú)效的時(shí)候拋出的。在這篇文章里,我們將討論:

      “ORA-04068”錯(cuò)誤是什么和它為什么發(fā)生,它會(huì)影響什么,以及建議的解決方法

      下面我們將從定義“ORA-04068”錯(cuò)誤開(kāi)始。

      注意: 在這篇文章的示例里使用的是Oracle 9.2.0.3,不過(guò)相同的概念在Oracle 10g 中應(yīng)該也是適用的。

      “ORA-04068”錯(cuò)誤是什么和它為什么發(fā)生?

      如果我們使用Oracle的oerr程序看看ORA-04068的定義,我們會(huì)得到下面的信息:

      $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.

      這個(gè)錯(cuò)誤顯示執(zhí)行包的現(xiàn)有狀態(tài)被另一個(gè)會(huì)話的一個(gè)動(dòng)作無(wú)效化了。這個(gè)“狀態(tài)”涉及包在規(guī)范或體中定義的任何全局變量(包括常量)。引起這個(gè)錯(cuò)誤的動(dòng)作一般是(但不局限于此)在得到了發(fā)生錯(cuò)誤的會(huì)話所使用的連接之后包的重新編譯。Oracle 建議的動(dòng)作是重新初始化應(yīng)用程序狀態(tài)以調(diào)整包的新?tīng)顟B(tài)后重新嘗試。

      如果我們看些例子就會(huì)明白得多。

      假設(shè)有下面定義的表:

      create table t (x number );

      有個(gè)叫做pkg 的包具有一個(gè)叫做p的存儲(chǔ)過(guò)程,如下所示:

      create or replace package pkg as
      procedure p;
      end pkg;
      /

      下面所顯示的包pkg的包體定義了存儲(chǔ)過(guò)程p只是插入一個(gè)常量1到我們先前定義的表t中去。

     create or replace package body pkg as
      procedure p
      is
      begin
      insert into t(x) values (1);
      end p;
      end pkg;
      /

      注意在包規(guī)范或包體中沒(méi)有全局變量或常量。換句話說(shuō),這個(gè)包是“無(wú)狀態(tài)的”。

      我們將使用兩個(gè)SQL*Plus 會(huì)話來(lái)解釋這個(gè)概念。在每一個(gè)“體驗(yàn)”中,我們會(huì)在每一個(gè)會(huì)話中編譯包體之后執(zhí)行存儲(chǔ)過(guò)程pkg.p。現(xiàn)在開(kāi)始體驗(yàn)1,在體驗(yàn)1中,即使我們?cè)诹硪粋€(gè)會(huì)話中編譯包體也不會(huì)出現(xiàn)ORA-04068錯(cuò)誤的。這是因?yàn)檫@個(gè)包是“無(wú)狀態(tài)的”,它在規(guī)范或體中沒(méi)有定義任何全局變量或常量。

      體驗(yàn)1

      假設(shè)表t和包pkg的規(guī)范以及包體已經(jīng)在包里定義了。在SQL*Plus 會(huì)話1中,我們執(zhí)行包并獲得下面的結(jié)果(這個(gè)包執(zhí)行成功)。

      注意:你可能注意到在這篇文章里啟動(dòng)SQL*Plus 有時(shí)和常規(guī)啟動(dòng)("SQL >")不一樣——例如,在下面代碼的“session 1”中。例如這可以使用命令“set sqlprompt 'session 1”來(lái)實(shí)現(xiàn)。

      session 1> exec pkg.p
      PL/SQL procedure successfully completed.

      在SQL*Plus會(huì)話2中,我們通過(guò)像下面這樣重新創(chuàng)建包來(lái)重新編譯它:

      session 2> create or replace package body pkg as
      2 procedure p
      3 is
      4 begin
      5 insert into t(x) values (1);
      6 end p;
      7 end pkg;
      8 /
      Package body created.
      session 2> show errors;
      No errors.

      現(xiàn)在如果你回到會(huì)話1并重新執(zhí)行包存儲(chǔ)過(guò)程p,它會(huì)成功執(zhí)行。

      session 1> exec pkg.p
      PL/SQL procedure successfully completed.

      讓我們看看到目前為止我們所做的。我們定義了一個(gè)簡(jiǎn)單的包,只具有一個(gè)插入一個(gè)常量到一個(gè)數(shù)據(jù)表中的存儲(chǔ)過(guò)程。我們開(kāi)啟了一個(gè)會(huì)話并執(zhí)行這個(gè)包存儲(chǔ)過(guò)程。在另一個(gè)會(huì)話中我們重新編譯這個(gè)包(通過(guò)重新創(chuàng)建它)。當(dāng)我們?cè)诘谝粋€(gè)會(huì)話中重新執(zhí)行這個(gè)包時(shí),它運(yùn)行正常——特別是,在會(huì)話2中包的重新編譯在會(huì)話1中存儲(chǔ)過(guò)程的第二次執(zhí)行沒(méi)有出現(xiàn)任何錯(cuò)誤。

    現(xiàn)在讓我們重復(fù)這整個(gè)過(guò)程,只改變一個(gè)地方——添加一個(gè)全局變量到包體中(添加到規(guī)范中是一樣的)。這意味著我們給包添加了“狀態(tài)”。我們?cè)谙乱还?jié)“體驗(yàn)2”中將講述只做了這一個(gè)改變的相同體驗(yàn)。

      體驗(yàn)2

      我們從之前的會(huì)話退出。開(kāi)啟一個(gè)新的會(huì)話并在會(huì)話1中編輯我們新的包體,如下所示——注意在包的開(kāi)始部分有一個(gè)常量聲明,如下面的粗體顯示。這是包的狀態(tài)。這個(gè)常量不會(huì)被使用,但是它是這個(gè)體驗(yàn)沒(méi)有得到結(jié)果的原因。

      session1>@pkg_body
      session1>create or replace package body pkg as
      2 g_constant constant number := 1;
      3 procedure p
      4 is
      5 begin
      6 insert into t(x) values (1);
      7 end p;
      8 end pkg;
      9 /
      Package body created.
      session1>show errors;
      No errors.

      現(xiàn)在我們?cè)跁?huì)話1中執(zhí)行存儲(chǔ)過(guò)程p。

      session 1> exec pkg.p
      PL/SQL procedure successfully completed.

      開(kāi)啟一個(gè)新的會(huì)話“session 2”并通過(guò)重新創(chuàng)建這個(gè)包來(lái)重新編譯它。

      session 2> @pkg_body
      session 2> create or replace package body pkg as
      2 g_constant constant number := 1;
      3 procedure p
      4 is
      5 begin
      6 insert into t(x) values (1);
      7 end p;
      8 end pkg;
      9 /
      Package body created.
      session 2> show errors;

    再次在會(huì)話1中執(zhí)行存儲(chǔ)過(guò)程p,得到下面的結(jié)果:

      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

      發(fā)生了什么?當(dāng)我們重新在會(huì)話2中編譯包體時(shí),我們重置了包的狀態(tài)。換句話說(shuō),對(duì)于任何在包編譯之前連接的會(huì)話,包的執(zhí)行狀態(tài)(在這種情況下,由在包體里指定給常量的值來(lái)定義)被從內(nèi)存中刪除了。注意,我們實(shí)際上沒(méi)有改變這個(gè)狀態(tài)(我們?cè)谥匦戮幾g的時(shí)候保持相同的常量值),但是Oracle沒(méi)有跟蹤這一級(jí)別上的細(xì)節(jié)。只要連接了Oracle,在會(huì)話2里,重新編譯了包pkg——這個(gè)包的狀態(tài)現(xiàn)在重置為一個(gè)新的狀態(tài)——所以對(duì)于任何在這個(gè)重新編譯發(fā)生之前已經(jīng)連接到Oracle的已有會(huì)話來(lái)說(shuō),包的狀態(tài)變?yōu)闊o(wú)效。因此任何存儲(chǔ)過(guò)程或包的函數(shù)下一次執(zhí)行時(shí),就會(huì)立即拋出ORA-04068錯(cuò)誤。

      如果我們?cè)诘谝淮螄L試中得到了ORA-04068錯(cuò)誤,那么我們?cè)跁?huì)話1中重新執(zhí)行這個(gè)包會(huì)發(fā)生什么呢?讓我們看一下。

      session 1> exec pkg.p
      PL/SQL procedure successfully completed.

      如同你所看到的,第二次執(zhí)行顯示調(diào)用應(yīng)用程序(在這個(gè)例子中是SQL*Plus)調(diào)整為新的狀態(tài)(自從Oracle通知了一次改變的狀態(tài))并重新執(zhí)行有了新?tīng)顟B(tài)的包。這是Oracle 所建議的動(dòng)作(查看這一節(jié)的開(kāi)始部分):

      在恰當(dāng)?shù)刂匦鲁跏蓟魏螒?yīng)用程序狀態(tài)之后重新嘗試。

      下面講述ORA-04068錯(cuò)誤的一些影響。

      “ORA-04068”錯(cuò)誤的影響

      要測(cè)量ORA-04068的影響,你所要做的一切就是google它。它的兩個(gè)主要影響如下:

      大多數(shù)企業(yè)應(yīng)用程序使用緩存連接的連接池。現(xiàn)在無(wú)論何時(shí)部署一個(gè)新的包規(guī)范,都需要在生產(chǎn)過(guò)程中重新編譯。你編譯的時(shí)候,對(duì)于連接池中的所有連接,這個(gè)包的狀態(tài)都會(huì)被無(wú)效化,因?yàn)檫@個(gè)包在獲得連接之后進(jìn)行了重新編譯(作為連接池初始化的一部分,有時(shí)更早)。注意,無(wú)論你是否改變這個(gè)狀態(tài),無(wú)論你是否改變代碼,都會(huì)如此。當(dāng)一個(gè)存儲(chǔ)過(guò)程或一個(gè)函數(shù)第一次被調(diào)用時(shí),它將會(huì)失敗并拋出“ORA-04058”錯(cuò)誤。所以一般情況下,你需要記住要“刷出”連接池(意味著丟棄現(xiàn)有連接并獲得到Oracle的新連接)。這通常導(dǎo)致應(yīng)用程序部署的一個(gè)停機(jī)。例如,如果你正在使用tomcat 和在tomcat 中的一個(gè)連接池,那么你可能需要停止tomcat 并重啟——以便它重新初始化連接池。那么如果有一個(gè)長(zhǎng)時(shí)間運(yùn)行的批處理正在使用一個(gè)連接來(lái)執(zhí)行與需要重新編譯的包完全無(wú)關(guān)的一些邏輯呢?那么你或者需要等待這個(gè)批處理執(zhí)行完畢或者在部署過(guò)程中將它關(guān)閉以便你可以重新初始化連接池。正如你可能想到的,這在應(yīng)用程序有效性方面會(huì)是個(gè)夢(mèng)魘。

      很糟糕的一個(gè)影響是開(kāi)發(fā)人員會(huì)困惑于為什么一個(gè)簡(jiǎn)單包(具有一個(gè)狀態(tài))的重新編譯會(huì)導(dǎo)致在Oracle中得到這個(gè)錯(cuò)誤。特別是在其它的數(shù)據(jù)庫(kù)例如SQL Server和MySQL沒(méi)有相同的包概念,因此沒(méi)有與存儲(chǔ)過(guò)程或函數(shù)相關(guān)聯(lián)的一個(gè)狀態(tài)。所以,在這些數(shù)據(jù)庫(kù)中,你可以重新部署存儲(chǔ)過(guò)程,并且應(yīng)用程序會(huì)透明地使用它們。對(duì)于其它的數(shù)據(jù)庫(kù)這是否是一個(gè)正確的選擇是具有爭(zhēng)議的,并且不屬于本篇文章要討論的范圍。除了了解ORA-04068錯(cuò)誤的根本原因以及怎樣處理它之外,這個(gè)錯(cuò)誤還會(huì)使得開(kāi)發(fā)人員放棄去一起使用存儲(chǔ)過(guò)程(并從而放棄了使用存儲(chǔ)過(guò)程所帶來(lái)的所有好處),并在他們的應(yīng)用程序代碼中嵌入SQL語(yǔ)句(例如在Java代碼中嵌入SQL)。

    那么解決方法是什么?

      在這一節(jié),我們將討論處理“ORA-04068”錯(cuò)誤的許多解決方法。每一個(gè)解決方法都在它的可用性方面具有一些局限性。這些解決方法還顯示了思考的過(guò)程,使得更容易理解推薦的解決方法和替代方法。

      讓我們從解決方法1開(kāi)始。

      解決方法1:使用無(wú)狀態(tài)包

      最簡(jiǎn)單的解決方法是在你的系統(tǒng)中只使用無(wú)狀態(tài)的包。正如我們?cè)谇懊嬲戮涮岬降模?dāng)你重新執(zhí)行一個(gè)無(wú)狀態(tài)的包時(shí)即使是它在另一個(gè)會(huì)話中重新編譯之后也不會(huì)發(fā)生ORA-04068錯(cuò)誤的。這是因?yàn)闆](méi)有可以被Oracle無(wú)效化的狀態(tài)。

      這個(gè)解決方法,雖然在理論方面很簡(jiǎn)單,但是具有以下明顯的缺陷:

      它使你不能定義任何狀態(tài),這導(dǎo)致代碼很差。一般有兩種類(lèi)型的狀態(tài):

      一個(gè)全局變量:一般應(yīng)該盡量避免全局變量。我遇到過(guò)的確需要在一個(gè)PL/SQL包或體中定義全局變量的合理需求。

      一個(gè)全局常量:幾乎所有的重要產(chǎn)品系統(tǒng)都需要定義常量。如果你決定在你的系統(tǒng)中不允許定義常量,那么就會(huì)導(dǎo)致很差的代碼、多次重復(fù)定義相同的值,當(dāng)需求改變的時(shí)候就會(huì)影響系統(tǒng)中不只一個(gè)地方,因此降低了可維護(hù)性。

      如果你已經(jīng)有一個(gè)包含了定義狀態(tài)的包的系統(tǒng),那么這個(gè)解決方法會(huì)使得進(jìn)行大量的重寫(xiě)。在這種情況下,你需要決定是否值得這么做。

      讓我們看下一個(gè)解決方法:

      解決方法2: 將所有的包狀態(tài)移到另一個(gè)包里

      這個(gè)解決方法的思想是將包體或包規(guī)范中的所有包狀態(tài)移到另一個(gè)包里,這個(gè)包作為“同伴狀態(tài)包”。這意味著我們降低了需要處理“ORA-06068”錯(cuò)誤的次數(shù),因?yàn)檫@些包本身并不存儲(chǔ)任何狀態(tài),盡管它們因?yàn)楦髯缘臓顟B(tài)而依賴(lài)于同伴包。在我的經(jīng)歷中,在包體執(zhí)行中發(fā)生的大多數(shù)改變——如果我們執(zhí)行這個(gè)解決方法那就不會(huì)導(dǎo)致ORA-06068錯(cuò)誤。如果我們重新編譯同伴狀態(tài)包,那仍然會(huì)發(fā)生ORA-06068錯(cuò)誤。

      讓我們看看這個(gè)解決方法的工作情況。

      我們創(chuàng)建一個(gè)新的包叫做const ,如下所示,我們將我們之前定義的常量移到我們的包pkg的包體中。

      create or replace package const as
      g_constant constant number := 1;
      end const;
      /
      show errors;

      包pkg的包規(guī)范沒(méi)有改變,并且為了你的方便,下面重復(fù)一下:

      create or replace package pkg as
      procedure p;
      end pkg;
      /
      show errors;

    這個(gè)包體改變了,以便它之中不再有常量定義(它移到了包c(diǎn)onst中),而且現(xiàn)在插入語(yǔ)句使用包c(diǎn)onst 中定義的常量以獲得這個(gè)值。因此包pkg依賴(lài)于包c(diǎn)onst以獲得由常量g_constant定義的它的狀態(tài):

      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;

      假設(shè)我們改變了對(duì)包pkg的包規(guī)范并在我們的系統(tǒng)中安裝了一個(gè)新的包c(diǎn)onst。現(xiàn)在我們登錄到我們的會(huì)話1中并執(zhí)行這個(gè)存儲(chǔ)過(guò)程——它如意料般地執(zhí)行成功:

      session 1>exec pkg.p
      PL/SQL procedure successfully completed.

      我們登錄到會(huì)話2中并重新編譯包pkg的這個(gè)包規(guī)范和包體:

      session 2>@pkg_spec
      session 2>create or replace package pkg as
      2 procedure p;
      3 end pkg;
      4 /
      Package created.
      session 2>show errors;
      No errors.
      session 2>@pkg_body
      session 2>create or replace package body pkg as
      2 procedure p
      3 is
      4 begin
      5 insert into t(x) values (const.g_constant);
      6 end p;
      7 end pkg;
      8 /
      Package body created.
      session 2>show errors;
      No errors.

    在會(huì)話1中,當(dāng)我們重新執(zhí)行這個(gè)存儲(chǔ)過(guò)程時(shí),盡管我們重新編譯了這個(gè)包規(guī)定和包體,它仍然執(zhí)行成功。這是因?yàn)檫@個(gè)包狀態(tài)是在包c(diǎn)onst中的(它已經(jīng)被重新編譯了),并因此當(dāng)我們重新編譯包pkg時(shí)包狀態(tài)沒(méi)有改變。.

      當(dāng)我們?nèi)缦略跁?huì)話2中重新編譯包c(diǎn)onst時(shí)會(huì)發(fā)生什么呢?:

      session 2>@const
      session 2>create or replace package const as
      2 g_constant constant number := 1;
      3 end const;
      4 /
      Package created.
      session 2>show errors;
      No errors.

      如果我們?cè)跁?huì)話1中重新執(zhí)行包pkg,我們將如意料般得到ORA-04068錯(cuò)誤。這個(gè)錯(cuò)誤清楚地表明在包c(diǎn)onst中的包狀態(tài)改變了,并因此使得依賴(lài)于它的包pkg被無(wú)效化。

      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

      當(dāng)然,如果我們之后在會(huì)話1中重新執(zhí)行這個(gè)包,它看起來(lái)如預(yù)期般執(zhí)行成功:

      session 1>exec pkg.p

      PL/SQL procedure successfully completed.

      解決方法2,盡管比解決方法1好些,但是具有以下缺陷:

      它要求你總要將包的狀態(tài)移到包外,因此使得包狀態(tài)對(duì)于系統(tǒng)中的其它所有的包來(lái)說(shuō)都是可見(jiàn)的。換句話說(shuō),你不能創(chuàng)建包私有的變量(或常量)(如果你在包體中聲明一個(gè)變量或常量,它不能被其它的包訪問(wèn)到——它是定義它的包所私有的——這使得更好地封裝代碼)。這削弱了系統(tǒng)的封裝性,從而降低了系統(tǒng)的可維護(hù)性。事實(shí)上,如果我們這么做,我們應(yīng)該只將常量作為任何包狀態(tài)的一部分(它是合理的并歡迎自我約束的)。

      它要求你將包的所有狀態(tài)移到一個(gè)同伴狀態(tài)包里。這導(dǎo)致系統(tǒng)中同伴包的增大,所以這個(gè)解決方法不太好。如果你決定只有一個(gè)包具有所有其它包的狀態(tài),那么你將遇到另一個(gè)問(wèn)題——在中央包里,一個(gè)變量或常量的改變會(huì)導(dǎo)致系統(tǒng)中所有其它包無(wú)效——甚至包括那些與這個(gè)變量或常量無(wú)關(guān)的包。只有你能決定這兩個(gè)選擇(中央狀態(tài)包或每個(gè)包的同伴狀態(tài)包)哪個(gè)適合于你。

      如果你已有系統(tǒng)具有定義了狀態(tài)的包,那么這個(gè)解決方法可能很難執(zhí)行,因?yàn)樗赡軐?dǎo)致大量的重寫(xiě)。這種情況下你需要衡量是每一次部署出現(xiàn)連續(xù)的ORA-04068錯(cuò)誤,還是要一次性重寫(xiě)系統(tǒng)。

      我們下一組的解決方法是針對(duì)于前面提到的兩個(gè)解決方法的改進(jìn),但它們有較大的缺陷,以至于在這篇文章里使得解決方法1或解決方法2是最終的推薦解決方法。但是,我強(qiáng)烈建議你看看下面兩個(gè)解決方法來(lái)了解它們的機(jī)制,并基于你對(duì)系統(tǒng)的了解來(lái)作出判斷。

    解決方法3: 監(jiān)測(cè)ORA-0408錯(cuò)誤并重新執(zhí)行包的存儲(chǔ)過(guò)程

      這個(gè)解決方法將處理錯(cuò)誤的責(zé)任放到了客戶端。它的思想是Oracle 通過(guò)生成錯(cuò)誤ORA-04068給客戶端提供了關(guān)于包狀態(tài)已經(jīng)被無(wú)效化的信息和由客戶端來(lái)監(jiān)測(cè)這個(gè)錯(cuò)誤以及作出反應(yīng)。客戶端可以選擇重新執(zhí)行這個(gè)存儲(chǔ)過(guò)程,如果它需要的話。我們已經(jīng)看到這個(gè)解決方法看起來(lái)是工作在SQL*Plus 中,當(dāng)存儲(chǔ)過(guò)程的執(zhí)行是在這個(gè)錯(cuò)誤如意料般的發(fā)生之后。我們現(xiàn)在將看看在使用JDBC的Java程序中它的執(zhí)行以及看看它是否管用。

      首先讓我們回到在包pkg中有狀態(tài)的舊代碼。所以我們?cè)诎w中重新引進(jìn)狀態(tài),像以前一樣——這個(gè)代碼復(fù)制到下面以方便你查看:

      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;

      假設(shè)我們重新編譯了包體以便我們具有恰當(dāng)?shù)男麓a。我們將首先在一個(gè)Java程序中使用JDBC進(jìn)行模擬一個(gè)會(huì)導(dǎo)致ORA-04068錯(cuò)誤的環(huán)境。為此我們將:

      使用JDBC在Java程序中獲得一個(gè)連接,

      在Java程序中使用JDBC執(zhí)行pkg.p 存儲(chǔ)過(guò)程,

      在Java程序中休眠一段時(shí)間(10到20秒),

      當(dāng)我們的Java 程序休眠時(shí),我們?cè)谝粋€(gè)單獨(dú)的SQL*Plus會(huì)話中重新編譯包pkg的包體,

      在Java程序中使用JDBC重新執(zhí)行pkg.p 存儲(chǔ)過(guò)程——這將導(dǎo)致ORA-04068錯(cuò)誤。

      叫做ExecutePackageProcedureTwice 的Java程序顯示如下。它執(zhí)行了pkg.p存儲(chǔ)過(guò)程,休眠了20秒以給我們充足的時(shí)間來(lái)重新編譯這個(gè)包以模擬部署,然后重新執(zhí)行這個(gè)存儲(chǔ)過(guò)程:

      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();
      }
      }

    現(xiàn)在讓我們?cè)偕鶲RA-04068錯(cuò)誤。

      設(shè)置恰當(dāng)?shù)腃LASSPATH路徑,指向類(lèi)的根路徑和classes12.jar(這個(gè)Jar包含Oracle JDBC執(zhí)行),執(zhí)行這個(gè)類(lèi),我們得到下面的結(jié)果:

      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...

      當(dāng)Java程序運(yùn)行到它開(kāi)始休眠的地方時(shí),我們?cè)谝粋€(gè)單獨(dú)的SQL*Plus會(huì)話中重新編譯這個(gè)包:

      SQL> @pkg_body
      SQL> create or replace package body pkg as
      2 g_constant constant number := 1;
      3 procedure p
      4 is
      5 begin
      6 insert into t(x) values (1);
      7 end p;
      8 end pkg;
      9 /
      Package body created.
      SQL> show errors;
      No errors.

      然后在Java程序結(jié)束休眠后,它如預(yù)期般地在試圖第二次執(zhí)行這個(gè)包的時(shí)候丟出ORA-04068錯(cuò)誤:

      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)

    現(xiàn)在,正如我們所說(shuō)的,我們知道在客戶端級(jí)別(在這個(gè)例子中是在Java程序中)通過(guò)丟出的異常我們可以監(jiān)測(cè)錯(cuò)誤的代碼并通過(guò)重新執(zhí)行這個(gè)包來(lái)作出響應(yīng)。最簡(jiǎn)單的執(zhí)行如修改過(guò)的程序dbj2ee.article2.design2.ExecutePackageProcedureTwice 所顯示——與第一個(gè)版本的不同之處用粗體顯示,便于你查看。正如你所看到的,我們捕捉SQLException 并查看這個(gè)錯(cuò)誤是否是ORA-04068——如果是,那我們重新執(zhí)行這個(gè)包,否則我們?cè)俅螔伋鲞@個(gè)錯(cuò)誤。

      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:@devhost:1521:ora92", "rmenon", "rmenon");
      }
      private static void executePkg(Connection conn, CallableStatement cstmt)
      throws Exception {
      System.out.println("Executing the package...");
      cstmt.executeUpdate();
      conn.commit();
      }
      }

    讓我們看看當(dāng)我們執(zhí)行這個(gè)程序并在另一個(gè)會(huì)話中編譯這個(gè)包的時(shí)候發(fā)生了什么。與前面一樣,我們執(zhí)行這個(gè)程序并得到下面的輸出:

      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...

      在另一個(gè)會(huì)話里,我們重新編譯這個(gè)包:

      SQL> @pkg_body
      SQL> create or replace package body pkg as
      2 g_constant constant number := 1;
      3 procedure p
      4 is
      5 begin
      6 insert into t(x) values (1);
      7 end p;
      8 end pkg;
      9 /
      Package body created.
      SQL> show errors;
      No errors.
      SQL>

      當(dāng)我們回到我們的Java程序時(shí),它輸出下面的信息作為重新執(zhí)行這個(gè)包的一部分:

      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...

      如你所看到的,我們監(jiān)測(cè)這個(gè)錯(cuò)誤并成功地重新執(zhí)行這個(gè)包。

      盡管這個(gè)解決方法看起來(lái)很好,但是它的缺陷很明顯:為了執(zhí)行它,我們將在我們的Java程序中每次調(diào)用一個(gè)存儲(chǔ)過(guò)程時(shí)都需要捕捉這個(gè)異常。這個(gè)更改在大多數(shù)系統(tǒng)中都是被禁止的。這個(gè)解決方法還有另一個(gè)缺陷,我將在后面提到,這個(gè)缺陷使這個(gè)解決方法不能用于許多系統(tǒng)。

      我們下一個(gè)潛在的解決方法精簡(jiǎn)了這一節(jié)中展示的解決方法,使得包的重新執(zhí)行對(duì)于一個(gè)已有的系統(tǒng)是透明的,因此使得它在這個(gè)系統(tǒng)中的執(zhí)行是切實(shí)可行的。

    解決方法4:透明地監(jiān)測(cè)ORA-0408錯(cuò)誤并重新執(zhí)行包的存儲(chǔ)過(guò)程

      這個(gè)解決方法的思想是:

      我們用我們自己的封裝類(lèi),叫做MyConnectionWrapper,來(lái)替代Oracle的連接執(zhí)行。做這個(gè)替代的最好地方是在驅(qū)動(dòng)級(jí)別——通過(guò)編寫(xiě)一個(gè)封裝驅(qū)動(dòng)——盡管你可以在連接池執(zhí)行級(jí)別做這個(gè)替代(例如在數(shù)據(jù)源中)。

      我們的連接封裝會(huì)返回一個(gè)叫做CallableStatement的CallableStatement封裝,而不是無(wú)論何時(shí)我們調(diào)用它之上的方法prepareCall()就執(zhí)行Oracle的CallableStatement。在其它所有的方法中,這個(gè)封裝類(lèi)會(huì)用這個(gè)動(dòng)作代替封裝的連接,因此它的行為和一個(gè)普通連接對(duì)象方式一樣。

      我們的CallableStatement 封裝會(huì)在調(diào)用它上面的“execute”方法時(shí)捕捉異常——如果它監(jiān)測(cè)到ORA-04068錯(cuò)誤,它會(huì)透明地對(duì)這個(gè)封裝的CallableStatement對(duì)象重新執(zhí)行這個(gè)方法。在其它所有的方法中,它會(huì)簡(jiǎn)單地以封裝的CallableStatement 對(duì)象來(lái)代表它。

      首先我們將執(zhí)行我們自己的驅(qū)動(dòng),這個(gè)驅(qū)動(dòng)執(zhí)行java.sql.Driver接口并封裝Oracle驅(qū)動(dòng)類(lèi)。MyDriverWrapper類(lèi)顯示如下:

      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.util.Properties;
      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;
      }
      }

    注意這個(gè)類(lèi)是怎樣定義它自己的私有前綴的——你可以設(shè)定任意的值。它還存儲(chǔ)了一個(gè)OracleDriver 對(duì)象的實(shí)例,它是做實(shí)際工作的。在連接方法中,驅(qū)動(dòng)在URL中進(jìn)行替代,它的具有Oracle thin driver前綴的私有前綴無(wú)縫地創(chuàng)建了一個(gè)適用于OracleDriver 的url。然后它通過(guò)指定OracleDriver實(shí)例獲得了Oracle連接。然后它封裝了這個(gè)連接和類(lèi)MyConnectionWrapper(過(guò)會(huì)兒我們將看看這個(gè)類(lèi))并返回了MyConnectionWrapper對(duì)象。這就是我們透明替代我們自己的連接對(duì)象的方式。注意,你可以以多種方式來(lái)進(jìn)行——例如,你可以在數(shù)據(jù)源級(jí)別替代這個(gè)連接,而不是在連接級(jí)別。

      類(lèi)MyConnectionWrapper 顯示如下。觀察下面關(guān)于這個(gè)類(lèi)執(zhí)行的信息:

      它將一個(gè)連接對(duì)象作為構(gòu)造器中的一個(gè)對(duì)象并將它存儲(chǔ)在一個(gè)私有的實(shí)例變量中。

      它封裝了所有版本prepareCall()方法執(zhí)行中的MyCallableStatement類(lèi)的所有CallableStatement對(duì)象。

      其它方法的執(zhí)行簡(jiǎn)單地用封裝連接中相應(yīng)的方法來(lái)代表它們的動(dòng)作。

      package dbj2ee.article2.design3;
      import java.sql.CallableStatement;
      import java.sql.Connection;
      import java.sql.DatabaseMetaData;
      import java.sql.PreparedStatement;
      import java.sql.SQLException;
      import java.sql.SQLWarning;
      import java.sql.Savepoint;
      import java.sql.Statement;
      import java.util.Map;
      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.
      }

    對(duì)于CallableStatement封裝類(lèi)的執(zhí)行,它的父接口PreparedStatement和Statement的執(zhí)行是必要的。因此我們創(chuàng)建三個(gè)封裝對(duì)象——MyStatementWrapper封裝了Statement對(duì)象;MyPreparedStatementWrapper封裝了PreparedStatement對(duì)象,而MyCallableStatementWrapper封裝了CallableStatement對(duì)象。

      MyStatementWrapper 類(lèi)是一個(gè)簡(jiǎn)單的封裝類(lèi),它封裝了Statement對(duì)象,下面顯示了它的一部分——這個(gè)代碼很容易理解:

      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.
      }

      MyPreparedStatementWrapper類(lèi)是一個(gè)簡(jiǎn)單封裝類(lèi),它封裝了PreparedStatement對(duì)象,下面顯示了它的一部分——這個(gè)代碼很容易理解:

      package dbj2ee.article2.design3;
      import java.net.URL;
      import java.sql.PreparedStatement;
      import java.sql.SQLException;
      import java.sql.ResultSet;
      import java.sql.Blob;
      import java.sql.Clob;
      import java.sql.ResultSetMetaData;
      import java.sql.Array;
      import java.io.InputStream;
      import java.math.BigDecimal;
      import java.io.Reader;
      import java.sql.Date;
      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.
      }

    MyCallableStatementWrapper 類(lèi)顯示如下。觀察下面關(guān)于這個(gè)類(lèi)的執(zhí)行信息:

      它擴(kuò)展了MyPreparedStatementWrapper類(lèi)。

      像其它的封裝類(lèi)一樣,它存儲(chǔ)一個(gè)CallableStatement對(duì)象作為它的實(shí)例變量的一部分。

      對(duì)于所有執(zhí)行存儲(chǔ)過(guò)程的方法來(lái)說(shuō),如果它透明地檢測(cè)到ORA-04068錯(cuò)誤,它就會(huì)覆蓋這個(gè)執(zhí)行,重新調(diào)用這個(gè)方法。注意,事實(shí)上,你可能還需要以類(lèi)似的方式覆蓋其它的一些從PreparedStatement 繼承而來(lái)的方法。

      其它方法的執(zhí)行簡(jiǎn)單地以封裝對(duì)象CallableStatement中的相關(guān)方法來(lái)代表它們的動(dòng)作。

      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.Blob;
      import java.sql.Clob;
      import java.sql.Array;
      import java.math.BigDecimal;
      import java.net.URL;
      import java.sql.Date;
      import java.util.Calendar;
      import java.sql.Ref;
      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.
      }

    最后,我們可以看看我們的使用了這個(gè)解決方法的ExecutePackageProcedureTwice 類(lèi)。它顯示如下。它和這一節(jié)開(kāi)頭的ExecutePackageProcedureTwice 非常類(lèi)似——除了以下不同(在類(lèi)的清單中以粗體顯示):

      它打印出連接和可調(diào)用的聲明類(lèi)來(lái)顯示這些類(lèi)確實(shí)存在于我們的封裝類(lèi)中。

      獲得連接的代碼首先確定我們的驅(qū)動(dòng)類(lèi)通過(guò)使用Class.forName()加載進(jìn)來(lái)了。然后使用我們私有的前綴而不是“oracle:jdbc:thin:”前綴,以便當(dāng)獲取連接時(shí)我們的驅(qū)動(dòng)會(huì)被DriverManager 選擇到,從而使得所有相關(guān)的JDBC類(lèi)都被替換為我們的封裝類(lèi)。

      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();
      }
      }

    當(dāng)我們執(zhí)行這個(gè)類(lèi)的時(shí)候,我們得到下面的結(jié)果(注意連接類(lèi)和可調(diào)用的聲明類(lèi)指向我們的封裝類(lèi)):

      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...

      然后我在另一個(gè)會(huì)話中像以前一樣重新編譯這個(gè)包:

      SQL> @pkg_body
      SQL> create or replace package body pkg as
      2 g_constant constant number := 1;
      3 procedure p
      4 is
      5 begin
      6 insert into t(x) values (1);
      7 end p;
      8 end pkg;
      9 /
      Package body created.
      SQL> show errors;
      No errors.

      當(dāng)我們回頭觀察我們的Java執(zhí)行時(shí),我們會(huì)在程序成功地重新執(zhí)行這個(gè)包后看到下面的內(nèi)容:

      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

      注意,如果你使用一個(gè)連接池那么你可以指定連接池里正確的驅(qū)動(dòng)使用相同的技術(shù)。

      因此我們可以設(shè)計(jì)這樣一個(gè)解決方法,它看起來(lái)對(duì)于幾乎所有的情況都是透明的。我曾經(jīng)以為這是最佳的解決方法。但是后來(lái)發(fā)現(xiàn)在這個(gè)看似最佳的解決方法中有些問(wèn)題。現(xiàn)在我描述一下。

      考慮下面的場(chǎng)景:

      你有一個(gè)包pkg ,它依賴(lài)于包c(diǎn)onst 中的一個(gè)常量。包pkg有兩個(gè)方法method1和method2,它們都依賴(lài)于常量const1——它的值設(shè)為1。

      你從連接池得到一個(gè)連接。

      在你的Java代碼中,你執(zhí)行方法pkg.method1——它使用常量的值,而這個(gè)常量的值現(xiàn)在是1。

      現(xiàn)在,作為部署的一部分,編譯包c(diǎn)onst ——常量的值變?yōu)榱?。

    你的事務(wù)執(zhí)行下一步——調(diào)用方法pkg.method2。

      因?yàn)槟阋呀?jīng)執(zhí)行了在這一節(jié)中提到的“默默的重新執(zhí)行技術(shù)”, method2 會(huì)默默地忽略O(shè)RA-04068并獲得常量的新值,現(xiàn)在是2。

      問(wèn)題是這可能導(dǎo)致事務(wù)中結(jié)果不一致。這是因?yàn)槟氵`反了這個(gè)假定——在包(或一般是包狀態(tài))中定義的常量應(yīng)該在一個(gè)給定會(huì)話中始終保持為一個(gè)相同值——否則就不能保證你依靠事務(wù)的語(yǔ)法得到一致的結(jié)果。

      因此這個(gè)解決方法在包的存儲(chǔ)過(guò)程重新執(zhí)行后不能給出正確結(jié)果的所有情況下是不可行的。這是可能發(fā)生的,比如舉例來(lái)說(shuō),你的包的存儲(chǔ)過(guò)程現(xiàn)在的執(zhí)行依賴(lài)于先前包的狀態(tài)。這種情況是比較常見(jiàn)的。

      總結(jié)和推薦的解決方法

      我們看了這篇文章中對(duì)于ORA-04068錯(cuò)誤的多個(gè)解決方法,并對(duì)于每一個(gè)解決方法都做了大量的評(píng)測(cè)。下面是我基于各個(gè)場(chǎng)景做出的建議:

      我建議不論在什么情況下,都盡可能地不要在包規(guī)范或包體中使用 全局變量。

      最簡(jiǎn)單的解決方法是使用無(wú)狀態(tài)的包(我們的解決方法1),如果可以這樣,那么這就是我所推薦的。你應(yīng)該努力使你的包無(wú)狀態(tài)化。

      第二個(gè)最佳解決方法(大多數(shù)情況下可行的方法)是為每一個(gè)包狀態(tài)為獨(dú)立的包添加同伴包。這確保了只有當(dāng)你真的改變了同伴狀態(tài)包的時(shí)候才會(huì)遇到ORA-04068錯(cuò)誤——這種情況應(yīng)該相對(duì)很少見(jiàn)——特別是當(dāng)狀態(tài)只包括常量的時(shí)候。如果你不想有同伴包,那么你可以有一個(gè)中央包包括系統(tǒng)中所有的常量——這會(huì)導(dǎo)致比一般更多的ORA-04048錯(cuò)誤——但是注意,即使你只改變了一個(gè)包的狀態(tài),你也需要刷出你的連接池,所以這不像它聽(tīng)起來(lái)的那么差。

      我不推薦解決方法4(或解決方法3),因?yàn)槲野l(fā)現(xiàn)很難擔(dān)保它們可以在任何復(fù)雜的系統(tǒng)中起作用。然而它們很少會(huì)失敗,這個(gè)解決方法就像一個(gè)定時(shí)炸彈一樣準(zhǔn)備好在這些很少見(jiàn)的環(huán)境中爆炸。

     

    posted on 2009-12-18 00:09 小菜毛毛 閱讀(3336) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): 數(shù)據(jù)庫(kù)
    主站蜘蛛池模板: 亚洲精品亚洲人成在线观看| 亚洲AV成人片色在线观看高潮| 亚洲第一AV网站| 亚洲一区电影在线观看| 免费无遮挡无遮羞在线看| 99re免费在线视频| 国产免费啪嗒啪嗒视频看看| 久久久久久久尹人综合网亚洲| 在线观看亚洲AV每日更新无码| 一区二区三区免费在线观看| 黄色永久免费网站| jlzzjlzz亚洲乱熟在线播放| 丁香婷婷亚洲六月综合色| 精品国产呦系列在线观看免费| 久久久久国色AV免费观看性色 | 亚洲日本在线观看| 国产精品亚洲二区在线| **毛片免费观看久久精品| 亚洲精品99久久久久中文字幕| 亚洲欧洲日韩综合| 中文字幕在线观看免费| 午夜小视频免费观看| 亚洲综合成人网在线观看| 十八禁的黄污污免费网站| 久久WWW色情成人免费观看| 亚洲VA中文字幕不卡无码| 色妞www精品视频免费看| 99久久99久久精品免费看蜜桃| 亚洲国产精品福利片在线观看| 边摸边吃奶边做爽免费视频99| 国产免费久久精品99re丫y| 亚洲成a人片在线观看无码专区 | 久久久久亚洲国产AV麻豆| 久视频精品免费观看99| 久久久久无码专区亚洲av| 亚洲av永久中文无码精品| 精品久久8x国产免费观看| 久久综合日韩亚洲精品色| 91av免费在线视频| 国产午夜鲁丝片AV无码免费| 亚洲AV无码乱码在线观看代蜜桃|