T_SEQUENCE
|
NAME
|
CURRENT_VAL
|
CUSTOMER
|
10
|
?
T_CUSTOMER
|
ID
|
CUSTOMER_NAME
|
9
|
Kevin
|
10
|
Mary
|
?
于是,在
Java
語言中,我們以面向?qū)ο蟮姆椒▉韺?shí)現(xiàn),可能會是這樣(常見寫法,未必是最優(yōu)實(shí)現(xiàn)):
publicclassCustomer{
publicvoidsequencePlus(){ Connectionconn=null; Statementstmt=null; try{ conn=getConnection(); stmt=conn.createStatement(); Stringsql="updateT_SEQUENCEsetCURRENT_VAL
=CURRENT_VAL+1" +"whereNAME='CUSTOMER'"; stmt.execute(sql); }catch(Exceptione){ e.printStackTrace(); }finally{ DbUtils.closeQuietly(stmt); DbUtils.closeQuietly(conn); } }
publicintgetSequenceCurrentVal(){ Connectionconn=null; Statementstmt=null; ResultSetrset=null; intid=0; try{ conn=getConnection(); stmt=conn.createStatement(); Stringsql="selectCURRENT_VALfromT_SEQUENCE
whereNAME='CUSTOMER'"; rset=stmt.executeQuery(sql); if(rset.next()){ id=rset.getInt(1); } }catch(Exceptione){ e.printStackTrace(); }finally{ DbUtils.closeQuietly(conn,stmt,rset); } returnid; }
publicvoidaddCustomer(Stringname){ Connectionconn=null; PreparedStatementstmt=null; ResultSetrset=null; try{ sequencePlus(); intid=getSequenceCurrentVal(); conn=getConnection(); stmt=conn.prepareStatement( "insertintoT_CUSTOMER(ID,CUSTOMER_NAME)values(?,?)"); stmt.setInt(1,id); stmt.setString(2,name==null?"":name); stmt.execute(); }catch(Exceptione){ e.printStackTrace(); }finally{ DbUtils.closeQuietly(stmt); DbUtils.closeQuietly(conn); } } }
|
<
示例代碼四
>
|
針對這種應(yīng)用場景,我們首先需要認(rèn)識到:上述的三個(gè)方法應(yīng)該屬于同一個(gè)數(shù)據(jù)庫事務(wù),否則,在并發(fā)情況下,將出現(xiàn)由于主鍵重復(fù)而導(dǎo)致數(shù)據(jù)插入失敗的情況。但同時(shí),我們也需要看到:即便上述三個(gè)方法的執(zhí)行位于同一個(gè)事務(wù)中,但三個(gè)方法使用的是不同的數(shù)據(jù)庫連接,雖然在
sequencePlus
方法中將
T_SEQUENCE
表中的數(shù)據(jù)加
1
,但在事務(wù)并未提交的情況下,由于
Connection
隔離級別的原因,在
getSequenceCurrentVal
方法中,是看不到
sequencePlus
方法中更新以后的數(shù)據(jù)的,這樣,也將導(dǎo)致數(shù)據(jù)插入失敗,因?yàn)橹麈I勢必跟舊有
ID
值重復(fù)。
因此,傳統(tǒng)的編程方法中,為克服上述問題,只有在上述的方法中使用同一個(gè)
Connection
,才能夠保證業(yè)務(wù)數(shù)據(jù)的正確。但這樣一來,將影響我們以
OO
方法分析問題時(shí)的“純潔”性,很容易讓人厭倦。
2.3.?
將Connection作為成員變量
另外一種常見的不當(dāng)編程模式是將
Connection
作為類的成員變量。一般來說,針對
Connection
,我們采取的策略是:用時(shí)再申請,用完立即釋放。而將
Connection
作為成員變量,將是對該規(guī)則的嚴(yán)重挑戰(zhàn),容易引起若干編程錯(cuò)誤。舉例而言:成員變量級的
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)樵?/span>
Java
語言內(nèi),你是無法控制對象的生命周期的。
將
Connection
作為成員變量還會產(chǎn)生另外一個(gè)問題:資源的閑置浪費(fèi)。因?yàn)樵谏暾堖B接以后,該資源將在這個(gè)對象的生命之期之內(nèi)一直有效,即使該對象處于非使用狀況,這無疑是一種資源的浪費(fèi)。更有甚者,倘若這種對象過多,將造成數(shù)據(jù)庫達(dá)到最大連接數(shù),造成應(yīng)用運(yùn)行失敗。
3.??????????
金蝶Apusic應(yīng)用服務(wù)器的數(shù)據(jù)源管理
金蝶
Apusic
應(yīng)用服務(wù)器支持業(yè)界主流的各種數(shù)據(jù)庫,在
Apusic
應(yīng)用服務(wù)器之內(nèi)進(jìn)行數(shù)據(jù)源的配置與使用都非常簡單,同時(shí),它提供了許多增值特性,能夠?yàn)閼?yīng)用的正常運(yùn)行提供額外的保障。
3.1.?
數(shù)據(jù)庫連接池的邏輯連接與物理連接
我們注意到:
java.sql.Connection
是一個(gè)
Interface
,那么,真正實(shí)現(xiàn)這個(gè)接口的類是什么呢?
我們可以做一個(gè)簡單的測試案例,在普通的
JavaApplication
中,調(diào)用如下方法:
publicvoidshowConnection(){ Connectionconn=null; try{ Class.forName("oracle.jdbc.driver.OracleDriver"); conn=DriverManager.getConnection(
"jdbc:oracle:thin:@localhost:1521:KEVINORA",
"system","manager"); System.out.println("ConnectionClassis:"+conn.getClass().getName()); }catch(Exceptione){ e.printStackTrace(); }finally{ DbUtils.closeQuietly(conn); } }
|
<
示例代碼五
>
|
得到的輸出結(jié)果是:
ConnectionClassis:
oracle.jdbc.driver.T4CConnection
而在
Apusic
應(yīng)用服務(wù)器中運(yùn)行如下方法:
publicvoidshowConnection(){ Connectionconn=null; try{ Contextctx=newInitialContext(); ds=(DataSource)ctx.lookup("jdbc/oracle"); conn=ds.getConnection(); System.out.println("ConnectionClassis:"+
conn.getClass().getName()); }catch(Exceptione){ e.printStackTrace(); }finally{ DbUtils.closeQuietly(conn); } }
|
<
示例代碼六
>
|
得到的輸出結(jié)果是:
ConnectionClassis:com.apusic.jdbc.adapter.ConnectionHandle
明明用相同的
JDBCDriver
連接同一個(gè)數(shù)據(jù)庫,為什么取得的
Connection
卻是不同的類呢?事實(shí)上,通過
Apusic
應(yīng)用服務(wù)器獲得的數(shù)據(jù)庫連接其實(shí)只是一個(gè)邏輯連接,真正的物理連接隱藏在該邏輯連接之內(nèi),這是一個(gè)典型的
Delegate
模式,而恰恰是這個(gè)模式,通過
Apusic
應(yīng)用服務(wù)器對數(shù)據(jù)源進(jìn)行管理,將給我們的應(yīng)用開發(fā)帶來很多好處:
3.2.?
當(dāng)事務(wù)結(jié)束以后,在該事務(wù)上下文中申請的物理連接,都將主動釋放
我們以一個(gè)最簡單的
StatelessSessionBean
為例:
publicclassSimpleBeanimplementsSessionBean{
publicvoidfoo(){ Connectionconn=null; try{ Contextctx=newInitialContext(); DataSourceds=(DataSource)ctx.lookup("jdbc/oracle"); conn=ds.getConnection(); System.out.println("notreleaseconnection"); }catch(Exceptione){ e.printStackTrace(); }finally{ //Notclosetheconnection //DbUtils.closeQuietly(conn); } } }
|
<
示例代碼七
>
|
SimpleBean
中的
foo
方法的事務(wù)屬性設(shè)置為
Required
,在該方法中,我們申請了一個(gè)數(shù)據(jù)庫連接,但并沒有釋放它,在運(yùn)行之前,我們通過
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>
|
由此,我們可以得知:即便由于程序的書寫錯(cuò)誤,沒能夠釋放申請的數(shù)據(jù)庫連接,但
Apusic
應(yīng)用服務(wù)器在事務(wù)完成之后,能夠把該事務(wù)上下文中申請的物理連接主動釋放,這對提升應(yīng)用的容錯(cuò)性帶來一定的好處。
3.3.?
當(dāng)jsp/servlet運(yùn)行結(jié)束以后,在jsp/servlet中申請的物理連接,都將主動釋放
同事務(wù)中申請的數(shù)據(jù)庫連接會主動釋放一樣,在
jsp/servlet
中申請的數(shù)據(jù)庫物理連接,當(dāng)
jsp/servlet
運(yùn)行完畢以后,如果用戶沒有釋放這些連接,
Apusic
應(yīng)用服務(wù)器也將予以主動釋放。讀者可以嘗試自己做一個(gè)案例:在
jsp
中申請一個(gè)連接,故意不釋放,在
jsp
執(zhí)行完畢以后,可以通過
SQLPlus
或者
Apusic
性能監(jiān)控工具,查看連接是否已經(jīng)被應(yīng)用服務(wù)器主動釋放。
由上述兩節(jié)內(nèi)容我們可以看到,
Apusic
應(yīng)用服務(wù)器能夠有效避免
2.1
節(jié)中所描述的問題。
3.4.?
ConnectionSharing:同一個(gè)事務(wù)上下文中申請的物理連接可以共享
通過共享連接可以更有效地使用資源及提高性能,并且可以防止連接之間的資源鎖定問題。
例如兩個(gè)
EJB
組件
A
和
B
,它們的事務(wù)屬性都設(shè)置為
Required
。在調(diào)用
EJBA
的方法時(shí)打開了一個(gè)數(shù)據(jù)庫連接,并對數(shù)據(jù)庫中的某個(gè)表進(jìn)行了更新操作,而在關(guān)閉連接之前
EJBA
調(diào)用了
EJBB
的某個(gè)方法,同樣
EJBB
打開同一個(gè)數(shù)據(jù)庫的連接,也對數(shù)據(jù)庫中同一個(gè)表進(jìn)行了更新操作。倘若沒有連接共享機(jī)制,這兩個(gè)連接指向的是兩個(gè)不同的物理連接,在其上執(zhí)行的數(shù)據(jù)庫操作將會互相鎖定,而這種死鎖狀態(tài)是無法恢復(fù)的。現(xiàn)在有了連接共享機(jī)制可以有效地解決這個(gè)問題。在
EJBA
和
B
中所獲得的連接對象實(shí)際上都指向同一個(gè)物理連接。這一個(gè)過程可以簡單描述如下:
con1=getConnection(); Transaction.begin performdatabaseoperationoncon1 con2=getConnection(); performdatabaseoperationoncon2 con2.close(); Transaction.commit(); con1.close();
|
<
示例代碼八
>
|
無論兩個(gè)連接是在事務(wù)邊界之內(nèi)或之外打開和關(guān)閉都沒有問題。只有在一個(gè)事務(wù)邊界之內(nèi)連接才會被共享,如果一個(gè)連接是在事務(wù)邊界之外打開的,那么在事務(wù)開始時(shí)會將此連接參與到事務(wù)中,并找到一個(gè)具有正確事務(wù)場景的物理連接和連接對象相關(guān)聯(lián)。在離開事務(wù)場景之后如果連接對象仍未關(guān)閉,則將其關(guān)聯(lián)到一個(gè)不具有事務(wù)場景的物理連接。
可以在部署描述中指定一個(gè)資源引用的
res-sharing-scope
屬性來允許或禁止連接共享,屬性值
shareable
為允許共享,
unshareable
為禁止共享,缺省情況下為允許共享。
回到
2.2
節(jié)中
Customer
那個(gè)測試案例,我們已經(jīng)說過,
Customer
的
sequencePlus
方法、
getSequenceCurrentVal
方法、以及
addCustomer
方法,需要放在一個(gè)事務(wù)中處理。但在這三個(gè)方法中,使用的是不同的
Connection
,而由于
Connection
的隔離級別,將導(dǎo)致插入
T_CUSTOMER
表中的
ID
主鍵將重復(fù),最終導(dǎo)致事務(wù)回滾。利用
Apusic
應(yīng)用服務(wù)器連接共享特性,能夠很好的解決這個(gè)問題。也就是說:雖然這三個(gè)方法申請的邏輯連接是不同的,但邏輯連接內(nèi)部所使用的物理連接是同一個(gè),這樣,將保證不同方法中對數(shù)據(jù)庫的操作結(jié)果相見可見,從而保證事務(wù)的正常提交。
舉例如下:假設(shè)在一個(gè)
jsp
文件中,這樣調(diào)用:
<%
????
InitialContext?ctx?=?
new?
InitialContext();
????
String?txName?=?
"java:comp/UserTransaction"
;
????
UserTransaction?tx?=?(UserTransaction)ctx.lookup(txName);
????
tx.begin();
????
new?
Customer().addCustomer(
"eric"
);
????
tx.commit();
%>
?
|
<
示例代碼九
>
|
在上述代碼中,通過
UserTransaction
啟動一個(gè)事務(wù),然后在該事務(wù)上下文中,增加一筆
Customer
的記錄,我們發(fā)覺,在不需要更改
Customer
類的情況下,上述方法能夠正常完成。
由此可以得知:在
Apusic
應(yīng)用服務(wù)器中進(jìn)行應(yīng)用的開發(fā),我們無需因?yàn)榭紤]數(shù)據(jù)庫
Connection
的隔離級別而影響我們對系統(tǒng)的面向?qū)ο蟮姆治龇椒ǎ?/span>
Apusic
應(yīng)用服務(wù)器將替我們保證在同一事務(wù)上下文中,使用相同的物理連接。
通過
Apusic
應(yīng)用服務(wù)器的這個(gè)特性,能夠有效的解決
2.2
節(jié)中描述的問題。
3.5.?
Lazy Connection Association Optimization:數(shù)據(jù)庫連接延遲關(guān)聯(lián)的優(yōu)化機(jī)制
在
3.1
節(jié)中我們談到:通過
Apusic
應(yīng)用服務(wù)器管理的數(shù)據(jù)庫連接分邏輯連接與物理連接,物理連接隱藏在邏輯連接的背后。那么,邏輯連接何時(shí)與一個(gè)真正的物理連接相關(guān)聯(lián)的呢?在關(guān)聯(lián)的過程之中,
Apusic
應(yīng)用服務(wù)器又提供了哪些優(yōu)化機(jī)制呢?舉例如下:
J2EE
組件可能會將連接對象保存在其實(shí)例變量中從而可以在多個(gè)事務(wù)之間重復(fù)使用,但是如果這個(gè)組件在使用一次之后就很少再被用到,那么系統(tǒng)資源將會被組件白白占用而得不到釋放,當(dāng)連接池被占滿時(shí)就再也無法獲得新的連接。
Lazy Connection Association Optimization
是這樣一種機(jī)制,當(dāng)
J2EE
組件方法調(diào)用完成時(shí),釋放連接對象所指向的物理連接以供其他組件使用,連接對象進(jìn)入一個(gè)
Inactive
狀態(tài),在這個(gè)狀態(tài)下它不和任何物理連接相關(guān)聯(lián)。當(dāng)
J2EE
組件需要使用該連接對象時(shí),容器將其激活,將其和一個(gè)實(shí)際的物理連接相關(guān)聯(lián)。這一過程對于應(yīng)用組件來說是完全透明的。
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ù)場景中,并與同一事務(wù)場景中的其他邏輯連接共享同一個(gè)物理連接。
我們在
2.3
節(jié)中強(qiáng)調(diào):將
Connection
作為成員變量是一種糟糕的設(shè)計(jì)模式,但同時(shí),我們也看到:哪怕用戶舊有系統(tǒng)中存在這樣的用法,
Apusic
應(yīng)用服務(wù)器也能夠很好的解決由于這種糟糕的設(shè)計(jì)所帶來的缺陷。
4.??????????
總結(jié)
本文首先與讀者分析了一些錯(cuò)誤或者不當(dāng)?shù)臄?shù)據(jù)庫資源使用方法,然后簡要介紹了金蝶
Apusic
應(yīng)用服務(wù)器在數(shù)據(jù)源管理上的一些特性。這些特性,對應(yīng)用的健壯性及容錯(cuò)性帶來一定的好處。但需要再次提醒的是:應(yīng)用服務(wù)器提供的一些增值特性,僅能夠當(dāng)作保障我們應(yīng)用正常運(yùn)行的最后一道屏障,我們切不可依賴于這些特性而忽視程序自身的編碼質(zhì)量。一個(gè)
J2EE
應(yīng)用能否正常的運(yùn)行,程序自身的設(shè)計(jì)與編碼永遠(yuǎn)是主要因素。
5.??????????
參考資料
注
2
:
CommonsDbUtils:ApacheJakarta
項(xiàng)目的
Commons
組件,
http://jakarta.apache.org/commons/index.html
注
3
:金蝶
Apusic
應(yīng)用服務(wù)器:國內(nèi)首家通過
J2EE1.4
認(rèn)證的應(yīng)用服務(wù)器,請參考
http://www.apusic.com/
?