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