金蝶Apusic应用服务器的数据源管理(转)
1.?????????? 前言
在基于 J2EE 平臺(tái)的應(yīng)用開發(fā)中,大多數(shù)的應(yīng)用都需要跟數(shù)據(jù)庫(kù)打交道;而自從接觸 JDBC 起,我們便不止一次的被告之:數(shù)據(jù)庫(kù)資源是十分寶貴的系統(tǒng)資源,一定要謹(jǐn)慎使用。但令人遺憾的是,在筆者見過的大部分跟數(shù)據(jù)庫(kù)相關(guān)的應(yīng)用開發(fā)中,針對(duì)數(shù)據(jù)庫(kù)資源的使用總是充斥著這樣或者那樣的問題。在本文中,筆者針對(duì)常見的一些錯(cuò)誤或者不當(dāng)?shù)氖褂脭?shù)據(jù)庫(kù)資源的案例進(jìn)行介紹與分析,并闡述金蝶 Apusic 應(yīng)用服務(wù)器提供的一些增值特性,通過這些特性能夠有效的避免某些錯(cuò)誤的發(fā)生。
2.?????????? 常見數(shù)據(jù)庫(kù)資源錯(cuò)誤/不當(dāng)用法的案例分析
2.1.? 未正確的關(guān)閉數(shù)據(jù)庫(kù)連接
申請(qǐng)了數(shù)據(jù)庫(kù)連接,卻沒有及時(shí)的關(guān)閉它,這幾乎是最常見的數(shù)據(jù)庫(kù)連接使用錯(cuò)誤。犯這種錯(cuò)誤的原因有很多,以下是常見的一種低級(jí)錯(cuò)誤:
publicvoidfoo(){
?????? Connectionconn=getConnection();
?????? Statementstmt=null;
?????? try{
?????? conn=getConnection();
?????? stmt=conn.createStatement();
?????? }catch(Exceptione){
?????? }finally{
?????? close(stmt,conn);
?????? }
}
< 示例代碼一 >
在上述案例中的第 2 行代碼中,作者已經(jīng)申請(qǐng)了一個(gè) Connection ,但在第 5 行代碼中,又申請(qǐng)了一個(gè)新的 Connection ,并且丟失了第一次申請(qǐng)的 connection 的引用,至此,當(dāng)程序每調(diào)一次 foo 方法,將導(dǎo)致申請(qǐng)一個(gè)新的 Connection 而沒有釋放它,如此一來(lái),當(dāng)數(shù)據(jù)庫(kù)達(dá)到能夠承受的最大連接數(shù)時(shí),將導(dǎo)致整個(gè)應(yīng)用的運(yùn)行失敗。
避免這種錯(cuò)誤的方法有很多,譬如,可采用類似于 FindBugs( 注 1) 的代碼分析工具對(duì)應(yīng)用的源碼進(jìn)行分析,找出可能產(chǎn)生錯(cuò)誤的代碼。
此外,在應(yīng)用中,我們需要非常頻繁的對(duì)申請(qǐng)的數(shù)據(jù)庫(kù)連接進(jìn)行關(guān)閉與釋放,此時(shí),建議封裝成某些工具類使用,并且要盡可能安全的關(guān)閉數(shù)據(jù)庫(kù)連接。下面,我們以關(guān)閉 Statement 及 Connection 的通用 close 方法的不同實(shí)現(xiàn)方案來(lái)比較:
不安全的關(guān)閉方法:
privatevoidclose(Statementstmt,Connectionconn){
?????? try{
????????????? stmt.close();
????????????? conn.close();
?????? }catch(Exceptione){}
}
< 示例代碼二 >
在上述代碼中,倘若第 3 行代碼中的 stmt 為空,或者 stmt.close() 方法出錯(cuò)并拋出異常,都將使第 4 行代碼不能夠正常調(diào)用,從而導(dǎo)致數(shù)據(jù)庫(kù)連接無(wú)法釋放,那么,更安全的寫法應(yīng)該是:
安全的關(guān)閉數(shù)據(jù)庫(kù)資源方法:
privatevoidclose(Statementstmt,Connectionconn){
?????? try{
????????????? if(stmt!=null)stmt.close();
?????? }catch(Exceptione){}
?????? try{
????????????? if(conn!=null)conn.close();
?????? }catch(Exceptione){}
}
< 示例代碼三 >
在修訂后的代碼中,我們可以看到,無(wú)論第 3 行代碼中關(guān)閉 stmt 是否成功,程序都能夠保證向下執(zhí)行,從而正確的關(guān)閉 conn 。
這些常用的數(shù)據(jù)庫(kù)資源操作公用類,可以使用 Apache 的 CommonsDbUtils( 注 2) 組件。
2.2.? 任意的申請(qǐng)數(shù)據(jù)庫(kù)連接
不考慮事務(wù)上下文,任意的申請(qǐng)數(shù)據(jù)庫(kù)連接資源,也是常見的一種不當(dāng)用法。但這種問題往往是難以克服的,根源在于 Java 是一種面向?qū)ο蟮恼Z(yǔ)言,而數(shù)據(jù)庫(kù)的事務(wù)卻是一種批量化的操作過程。我們以常見的“序列號(hào)”的實(shí)現(xiàn)方案為例:在某些應(yīng)用場(chǎng)景中,我們需要一種自增長(zhǎng)的整數(shù)型字段,但由于不同的數(shù)據(jù)庫(kù)有不同的實(shí)現(xiàn),所以,為達(dá)到各個(gè)數(shù)據(jù)庫(kù)兼容的目的,我們常用的解決方案是,新建一張 T_SEQUENCE 表,它可能包含的字段有: NAMEvarchar(100),CURRENT_VALnumber(10) ;其中, NAME 存放序列的名稱,而 CURRENT_VAL 存放序列的當(dāng)前值。假設(shè)某一業(yè)務(wù)對(duì)象 Customer 需要新增一筆記錄時(shí),為獲得不重復(fù)且自增長(zhǎng)的 CustomerID ,需要將 T_SEQUENCE 表中的與該業(yè)務(wù)表對(duì)應(yīng)的序列號(hào)加 1 并更新,然后將更新后的值作為 Customer 的 ID ,如下述表格所示:
| T_SEQUENCE | |
| NAME | CURRENT_VAL |
| CUSTOMER | 10 |
?
| T_CUSTOMER | |
| ID | CUSTOMER_NAME |
| 9 | Kevin |
| 10 | Mary |
?
于是,在 Java 語(yǔ)言中,我們以面向?qū)ο蟮姆椒▉?lái)實(shí)現(xiàn),可能會(huì)是這樣(常見寫法,未必是最優(yōu)實(shí)現(xiàn)):
| publicclassCustomer{ =CURRENT_VAL+1" whereNAME='CUSTOMER'"; |
| < 示例代碼四 > |
針對(duì)這種應(yīng)用場(chǎng)景,我們首先需要認(rèn)識(shí)到:上述的三個(gè)方法應(yīng)該屬于同一個(gè)數(shù)據(jù)庫(kù)事務(wù),否則,在并發(fā)情況下,將出現(xiàn)由于主鍵重復(fù)而導(dǎo)致數(shù)據(jù)插入失敗的情況。但同時(shí),我們也需要看到:即便上述三個(gè)方法的執(zhí)行位于同一個(gè)事務(wù)中,但三個(gè)方法使用的是不同的數(shù)據(jù)庫(kù)連接,雖然在 sequencePlus 方法中將 T_SEQUENCE 表中的數(shù)據(jù)加 1 ,但在事務(wù)并未提交的情況下,由于 Connection 隔離級(jí)別的原因,在 getSequenceCurrentVal 方法中,是看不到 sequencePlus 方法中更新以后的數(shù)據(jù)的,這樣,也將導(dǎo)致數(shù)據(jù)插入失敗,因?yàn)橹麈I勢(shì)必跟舊有 ID 值重復(fù)。
因此,傳統(tǒng)的編程方法中,為克服上述問題,只有在上述的方法中使用同一個(gè) Connection ,才能夠保證業(yè)務(wù)數(shù)據(jù)的正確。但這樣一來(lái),將影響我們以 OO 方法分析問題時(shí)的“純潔”性,很容易讓人厭倦。
2.3.? 將Connection作為成員變量
另外一種常見的不當(dāng)編程模式是將 Connection 作為類的成員變量。一般來(lái)說,針對(duì) Connection ,我們采取的策略是:用時(shí)再申請(qǐng),用完立即釋放。而將 Connection 作為成員變量,將是對(duì)該規(guī)則的嚴(yán)重挑戰(zhàn),容易引起若干編程錯(cuò)誤。舉例而言:成員變量級(jí)的 Connection ,何時(shí)創(chuàng)建?何時(shí)釋放?倘若在每一個(gè)方法體內(nèi)進(jìn)行 Connection 的創(chuàng)建與釋放,那么將 Connection 作為成員變量又失去了意義;倘若在類的構(gòu)造期內(nèi)進(jìn)行 Connection 的創(chuàng)建,那么又在何時(shí)釋放它呢?因?yàn)樵?Java 語(yǔ)言內(nèi),你是無(wú)法控制對(duì)象的生命周期的。
將 Connection 作為成員變量還會(huì)產(chǎn)生另外一個(gè)問題:資源的閑置浪費(fèi)。因?yàn)樵谏暾?qǐng)連接以后,該資源將在這個(gè)對(duì)象的生命之期之內(nèi)一直有效,即使該對(duì)象處于非使用狀況,這無(wú)疑是一種資源的浪費(fèi)。更有甚者,倘若這種對(duì)象過多,將造成數(shù)據(jù)庫(kù)達(dá)到最大連接數(shù),造成應(yīng)用運(yùn)行失敗。
3.?????????? 金蝶Apusic應(yīng)用服務(wù)器的數(shù)據(jù)源管理
金蝶 Apusic 應(yīng)用服務(wù)器支持業(yè)界主流的各種數(shù)據(jù)庫(kù),在 Apusic 應(yīng)用服務(wù)器之內(nèi)進(jìn)行數(shù)據(jù)源的配置與使用都非常簡(jiǎn)單,同時(shí),它提供了許多增值特性,能夠?yàn)閼?yīng)用的正常運(yùn)行提供額外的保障。
3.1.? 數(shù)據(jù)庫(kù)連接池的邏輯連接與物理連接
我們注意到: java.sql.Connection 是一個(gè) Interface ,那么,真正實(shí)現(xiàn)這個(gè)接口的類是什么呢?
我們可以做一個(gè)簡(jiǎn)單的測(cè)試案例,在普通的 JavaApplication 中,調(diào)用如下方法:
| publicvoidshowConnection(){ "jdbc:oracle:thin:@localhost:1521:KEVINORA", "system","manager"); |
| < 示例代碼五 > |
得到的輸出結(jié)果是: ConnectionClassis:
oracle.jdbc.driver.T4CConnection
而在 Apusic 應(yīng)用服務(wù)器中運(yùn)行如下方法:
| publicvoidshowConnection(){ conn.getClass().getName()); |
| < 示例代碼六 > |
得到的輸出結(jié)果是: ConnectionClassis:com.apusic.jdbc.adapter.ConnectionHandle
明明用相同的 JDBCDriver 連接同一個(gè)數(shù)據(jù)庫(kù),為什么取得的 Connection 卻是不同的類呢?事實(shí)上,通過 Apusic 應(yīng)用服務(wù)器獲得的數(shù)據(jù)庫(kù)連接其實(shí)只是一個(gè)邏輯連接,真正的物理連接隱藏在該邏輯連接之內(nèi),這是一個(gè)典型的 Delegate 模式,而恰恰是這個(gè)模式,通過 Apusic 應(yīng)用服務(wù)器對(duì)數(shù)據(jù)源進(jìn)行管理,將給我們的應(yīng)用開發(fā)帶來(lái)很多好處:
3.2.? 當(dāng)事務(wù)結(jié)束以后,在該事務(wù)上下文中申請(qǐng)的物理連接,都將主動(dòng)釋放
我們以一個(gè)最簡(jiǎn)單的 StatelessSessionBean 為例:
| publicclassSimpleBeanimplementsSessionBean{ |
| < 示例代碼七 > |
SimpleBean 中的 foo 方法的事務(wù)屬性設(shè)置為 Required ,在該方法中,我們申請(qǐng)了一個(gè)數(shù)據(jù)庫(kù)連接,但并沒有釋放它,在運(yùn)行之前,我們通過 SQLPlus 觀察 Oracle 數(shù)據(jù)庫(kù)的 Session ,得到的結(jié)果是:
| SQL> select count(*) from v$session; ??COUNT(*) ---------- ??????? 18 ? |
| < 圖一執(zhí)行方法之前的 OracleSession> |
而在執(zhí)行完 SimpleBean 的 foo 方法之后,我們?cè)俅斡^察 Oracle 數(shù)據(jù)庫(kù)的 Session ,得到的結(jié)果是:
| SQL> select count(*) from v$session; ? ??COUNT(*) ---------- ??????? 18 ? |
| < 圖二:執(zhí)行方法之后的 OracleSession> |
由此,我們可以得知:即便由于程序的書寫錯(cuò)誤,沒能夠釋放申請(qǐng)的數(shù)據(jù)庫(kù)連接,但 Apusic 應(yīng)用服務(wù)器在事務(wù)完成之后,能夠把該事務(wù)上下文中申請(qǐng)的物理連接主動(dòng)釋放,這對(duì)提升應(yīng)用的容錯(cuò)性帶來(lái)一定的好處。
3.3.? 當(dāng)jsp/servlet運(yùn)行結(jié)束以后,在jsp/servlet中申請(qǐng)的物理連接,都將主動(dòng)釋放
同事務(wù)中申請(qǐng)的數(shù)據(jù)庫(kù)連接會(huì)主動(dòng)釋放一樣,在 jsp/servlet 中申請(qǐng)的數(shù)據(jù)庫(kù)物理連接,當(dāng) jsp/servlet 運(yùn)行完畢以后,如果用戶沒有釋放這些連接, Apusic 應(yīng)用服務(wù)器也將予以主動(dòng)釋放。讀者可以嘗試自己做一個(gè)案例:在 jsp 中申請(qǐng)一個(gè)連接,故意不釋放,在 jsp 執(zhí)行完畢以后,可以通過 SQLPlus 或者 Apusic 性能監(jiān)控工具,查看連接是否已經(jīng)被應(yīng)用服務(wù)器主動(dòng)釋放。
由上述兩節(jié)內(nèi)容我們可以看到, Apusic 應(yīng)用服務(wù)器能夠有效避免 2.1 節(jié)中所描述的問題。
3.4.? ConnectionSharing:同一個(gè)事務(wù)上下文中申請(qǐng)的物理連接可以共享
通過共享連接可以更有效地使用資源及提高性能,并且可以防止連接之間的資源鎖定問題。
例如兩個(gè) EJB 組件 A 和 B ,它們的事務(wù)屬性都設(shè)置為 Required 。在調(diào)用 EJBA 的方法時(shí)打開了一個(gè)數(shù)據(jù)庫(kù)連接,并對(duì)數(shù)據(jù)庫(kù)中的某個(gè)表進(jìn)行了更新操作,而在關(guān)閉連接之前 EJBA 調(diào)用了 EJBB 的某個(gè)方法,同樣 EJBB 打開同一個(gè)數(shù)據(jù)庫(kù)的連接,也對(duì)數(shù)據(jù)庫(kù)中同一個(gè)表進(jìn)行了更新操作。倘若沒有連接共享機(jī)制,這兩個(gè)連接指向的是兩個(gè)不同的物理連接,在其上執(zhí)行的數(shù)據(jù)庫(kù)操作將會(huì)互相鎖定,而這種死鎖狀態(tài)是無(wú)法恢復(fù)的。現(xiàn)在有了連接共享機(jī)制可以有效地解決這個(gè)問題。在 EJBA 和 B 中所獲得的連接對(duì)象實(shí)際上都指向同一個(gè)物理連接。這一個(gè)過程可以簡(jiǎn)單描述如下:
| con1=getConnection(); |
| < 示例代碼八 > |
無(wú)論兩個(gè)連接是在事務(wù)邊界之內(nèi)或之外打開和關(guān)閉都沒有問題。只有在一個(gè)事務(wù)邊界之內(nèi)連接才會(huì)被共享,如果一個(gè)連接是在事務(wù)邊界之外打開的,那么在事務(wù)開始時(shí)會(huì)將此連接參與到事務(wù)中,并找到一個(gè)具有正確事務(wù)場(chǎng)景的物理連接和連接對(duì)象相關(guān)聯(lián)。在離開事務(wù)場(chǎng)景之后如果連接對(duì)象仍未關(guān)閉,則將其關(guān)聯(lián)到一個(gè)不具有事務(wù)場(chǎng)景的物理連接。
可以在部署描述中指定一個(gè)資源引用的 res-sharing-scope 屬性來(lái)允許或禁止連接共享,屬性值 shareable 為允許共享, unshareable 為禁止共享,缺省情況下為允許共享。
回到 2.2 節(jié)中 Customer 那個(gè)測(cè)試案例,我們已經(jīng)說過, Customer 的 sequencePlus 方法、 getSequenceCurrentVal 方法、以及 addCustomer 方法,需要放在一個(gè)事務(wù)中處理。但在這三個(gè)方法中,使用的是不同的 Connection ,而由于 Connection 的隔離級(jí)別,將導(dǎo)致插入 T_CUSTOMER 表中的 ID 主鍵將重復(fù),最終導(dǎo)致事務(wù)回滾。利用 Apusic 應(yīng)用服務(wù)器連接共享特性,能夠很好的解決這個(gè)問題。也就是說:雖然這三個(gè)方法申請(qǐng)的邏輯連接是不同的,但邏輯連接內(nèi)部所使用的物理連接是同一個(gè),這樣,將保證不同方法中對(duì)數(shù)據(jù)庫(kù)的操作結(jié)果相見可見,從而保證事務(wù)的正常提交。
舉例如下:假設(shè)在一個(gè) jsp 文件中,這樣調(diào)用:
| <% ? |
| < 示例代碼九 > |
在上述代碼中,通過 UserTransaction 啟動(dòng)一個(gè)事務(wù),然后在該事務(wù)上下文中,增加一筆 Customer 的記錄,我們發(fā)覺,在不需要更改 Customer 類的情況下,上述方法能夠正常完成。
由此可以得知:在 Apusic 應(yīng)用服務(wù)器中進(jìn)行應(yīng)用的開發(fā),我們無(wú)需因?yàn)榭紤]數(shù)據(jù)庫(kù) Connection 的隔離級(jí)別而影響我們對(duì)系統(tǒng)的面向?qū)ο蟮姆治龇椒?#xff0c; Apusic 應(yīng)用服務(wù)器將替我們保證在同一事務(wù)上下文中,使用相同的物理連接。
通過 Apusic 應(yīng)用服務(wù)器的這個(gè)特性,能夠有效的解決 2.2 節(jié)中描述的問題。
3.5.? Lazy Connection Association Optimization:數(shù)據(jù)庫(kù)連接延遲關(guān)聯(lián)的優(yōu)化機(jī)制
在 3.1 節(jié)中我們談到:通過 Apusic 應(yīng)用服務(wù)器管理的數(shù)據(jù)庫(kù)連接分邏輯連接與物理連接,物理連接隱藏在邏輯連接的背后。那么,邏輯連接何時(shí)與一個(gè)真正的物理連接相關(guān)聯(lián)的呢?在關(guān)聯(lián)的過程之中, Apusic 應(yīng)用服務(wù)器又提供了哪些優(yōu)化機(jī)制呢?舉例如下:
J2EE 組件可能會(huì)將連接對(duì)象保存在其實(shí)例變量中從而可以在多個(gè)事務(wù)之間重復(fù)使用,但是如果這個(gè)組件在使用一次之后就很少再被用到,那么系統(tǒng)資源將會(huì)被組件白白占用而得不到釋放,當(dāng)連接池被占滿時(shí)就再也無(wú)法獲得新的連接。 Lazy Connection Association Optimization 是這樣一種機(jī)制,當(dāng) J2EE 組件方法調(diào)用完成時(shí),釋放連接對(duì)象所指向的物理連接以供其他組件使用,連接對(duì)象進(jìn)入一個(gè) Inactive 狀態(tài),在這個(gè)狀態(tài)下它不和任何物理連接相關(guān)聯(lián)。當(dāng) J2EE 組件需要使用該連接對(duì)象時(shí),容器將其激活,將其和一個(gè)實(shí)際的物理連接相關(guān)聯(lián)。這一過程對(duì)于應(yīng)用組件來(lái)說是完全透明的。 J2EE 程序員經(jīng)常犯的一個(gè)錯(cuò)誤是忘記關(guān)閉連接,特別是發(fā)生異常時(shí)沒有執(zhí)行正確的清理,過去我們解決這一問題是在方法調(diào)用完成時(shí)強(qiáng)制關(guān)閉所有的連接,現(xiàn)在有了 Lazy Connection Association Optimization 機(jī)制可以更完美地解決這一問題。
ConnectionSharing 和 Lazy Connection Association Optimization 是同時(shí)起作用的,例如,當(dāng)一個(gè)連接被激活時(shí),它將被包含在當(dāng)前事務(wù)場(chǎng)景中,并與同一事務(wù)場(chǎng)景中的其他邏輯連接共享同一個(gè)物理連接。
我們?cè)?2.3 節(jié)中強(qiáng)調(diào):將 Connection 作為成員變量是一種糟糕的設(shè)計(jì)模式,但同時(shí),我們也看到:哪怕用戶舊有系統(tǒng)中存在這樣的用法, Apusic 應(yīng)用服務(wù)器也能夠很好的解決由于這種糟糕的設(shè)計(jì)所帶來(lái)的缺陷。
4.?????????? 總結(jié)
本文首先與讀者分析了一些錯(cuò)誤或者不當(dāng)?shù)臄?shù)據(jù)庫(kù)資源使用方法,然后簡(jiǎn)要介紹了金蝶 Apusic 應(yīng)用服務(wù)器在數(shù)據(jù)源管理上的一些特性。這些特性,對(duì)應(yīng)用的健壯性及容錯(cuò)性帶來(lái)一定的好處。但需要再次提醒的是:應(yīng)用服務(wù)器提供的一些增值特性,僅能夠當(dāng)作保障我們應(yīng)用正常運(yùn)行的最后一道屏障,我們切不可依賴于這些特性而忽視程序自身的編碼質(zhì)量。一個(gè) J2EE 應(yīng)用能否正常的運(yùn)行,程序自身的設(shè)計(jì)與編碼永遠(yuǎn)是主要因素。
5.?????????? 參考資料
注 1 : FindBugs : Sourceforge 上的一個(gè)開源工具,能夠?qū)υ创a進(jìn)行分析從而發(fā)現(xiàn)可能出現(xiàn)的編程錯(cuò)誤, http://findbugs.sourceforge.net/
注 2 : CommonsDbUtils:ApacheJakarta 項(xiàng)目的 Commons 組件, http://jakarta.apache.org/commons/index.html
注 3 :金蝶 Apusic 應(yīng)用服務(wù)器:國(guó)內(nèi)首家通過 J2EE1.4 認(rèn)證的應(yīng)用服務(wù)器,請(qǐng)參考 http://www.apusic.com/
?
轉(zhuǎn)載于:https://www.cnblogs.com/zhuyx/archive/2007/06/07/10402058.html
總結(jié)
以上是生活随笔為你收集整理的金蝶Apusic应用服务器的数据源管理(转)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 原神定序试炼其二挑战任务达成攻略详解(完
- 下一篇: “明白古所难”下一句是什么