摘要: Java
基礎(chǔ)方面
:
... 閱讀全文
JDBC高級應(yīng)用三
再談JDBC連結(jié)
為什么要反復(fù)談JDBC連結(jié),因為所以JDBC的性能,最主要的就是JDBC連結(jié),而SQL語句的
優(yōu)化,和JAVA編程無關(guān),你的一個查詢語句的效率,是你對于SQL語法的使用技巧,這一方
面你就可請教DBA,而不是來看我這種程序設(shè)計者的文章.
我們已經(jīng)知道,取得數(shù)據(jù)庫連結(jié),有幾種層次的實現(xiàn)方法,一是直接得到物理連結(jié),而是通
過傳統(tǒng)的連結(jié)池(沒有多大區(qū)別),三是通過java的擴展包javax.sql.DataSource得到連結(jié)
句柄,對于上面兩種,沒有什么可以多說的,對于DataSource,我們再深入一些.
一般來說,DataSource是容器本身作為一個JDNI查找的對象返回出來的,也就是說要依賴
容器進行配置,而如果一個100%的應(yīng)用程序(Application),比如基于swing的App,我根本
不需要運行容器,那我如何取得DataSource對象?這時可能要你自己寫基于DataSource的
連結(jié)池了(是不是有些太深入了?要做就做高手,SUN能做我們就能做).
如果自己要實現(xiàn)DataSource,要清楚幾個關(guān)系,即DataSource中返回的Connection是一個連
結(jié)的句柄,它要和實際的物理連結(jié)關(guān)連,這些實際的物理連結(jié)就是PooledConnection,我們
叫它池中的連結(jié),可以通過實現(xiàn)ConnectionPoolDataSource,從中得到PooledConnection,
這部分本來是廠商實現(xiàn)的,但這部份實現(xiàn)和傳統(tǒng)的連結(jié)池沒有什么大的區(qū)別,主要是從
ConnectionPoolDataSource中得到PooledConnection的物理連結(jié),但如何從PooledConnection
中g(shù)etConnection(),返回給用戶.這部分實現(xiàn)就是DataSource實現(xiàn)的性能高低的關(guān)鍵,一般
來說,我們可以先把一個物理連結(jié)PooledConnection和多個客戶連結(jié)相關(guān)連來增加性能,也
就是一個PooledConnection本身再作為一個工場的種子,通過一個PooledConnection再返回
多個Connection,說白了就是多個Connection的請求通過一個PooledConnection傳遞給數(shù)據(jù)庫.
只要用戶調(diào)用Connection的close()方法,就打斷這個Connetion與PooledConnection的關(guān)聯(lián)而
讓PooledConnection可以和新的Connection進行關(guān)聯(lián).
JDBC事務(wù)
JDBC1開始,就支持本地事務(wù),所謂要地事務(wù),就是在一個連結(jié)中的多個操作可以作為一個事務(wù)
過程來提交.注意,只要你使用conn.setAutoCommit(false);方法就隱式地打開了一個事務(wù).當(dāng)
事務(wù)被commit或abort時,隱含的是打開了一個新的事務(wù).
另外,當(dāng)一次事務(wù)被commit或abort,PreparedSattement和CallableStatement綁定的結(jié)果集全
部被關(guān)閉,而普通的Statement綁一的結(jié)果集將被維持.
在處理多個操作時:
conn.setAutoCommit(false);
Statement st1 = conn.createSatatement(sql1);
Statement st2 = conn.createSatatement(sql2);
Statement st3 = conn.createSatatement(sql3);
Statement st4 = conn.createSatatement(sql4);
st1.executeXXXXX();
st2.executeXXXXX();
st3.executeXXXXX();
st4.executeXXXXX();
在這里,我們要么把四個操作一起回滾,或一起提交,但如果我們認為,1-3的操作是要求一致完
成,而4的操作在一至三完成時再完成.在經(jīng)前的版本中我們要把它們分開在兩次事務(wù)中,但在
JDBC3.0以后,我們可以利用conn.setSavepoint() 來得到一個SavePoint來對同一事務(wù)中不同階
段進行斷點保存.然后可以在任何斷點上進行提交和回滾.同時我們還可以利用
conn.setTransactionIsolation()來設(shè)置隔離級別.
要注意的是,對事務(wù)的支持要看數(shù)據(jù)庫的具體實現(xiàn).如果數(shù)據(jù)庫本身不支持事務(wù),那么以上的操作
都是無效的,可以從 DatabaseMetaData中查詢數(shù)據(jù)庫對事務(wù)的支持情況.
畢竟,本地事務(wù)功能并不是很強,而如果不是編程人員對SQL語句傳入錯誤,那么在一次連結(jié)中
多個操作只完成部份的機率并不容易發(fā)生(當(dāng)然有時還會發(fā)生的,要不本地事務(wù)就不會產(chǎn)生了).
其實,JDBC事務(wù)最重要的是分布式事務(wù),即同時操作不同的連結(jié),可能是同物理庫的不同空間,也
可能是同一主機的不同數(shù)據(jù)庫或不同主機的多個數(shù)據(jù)庫.這就很難保證每個操作都是成功的,發(fā)
生操作不一致的機會太多了,可以說如果不在事務(wù)中測試你就無法相信操作的一致性.所以分布
式事務(wù)是JDBC的重要技術(shù).
在下一節(jié)我們重點介紹JDBC分布式事務(wù).
再談JDBC連結(jié)
為什么要反復(fù)談JDBC連結(jié),因為所以JDBC的性能,最主要的就是JDBC連結(jié),而SQL語句的
優(yōu)化,和JAVA編程無關(guān),你的一個查詢語句的效率,是你對于SQL語法的使用技巧,這一方
面你就可請教DBA,而不是來看我這種程序設(shè)計者的文章.
我們已經(jīng)知道,取得數(shù)據(jù)庫連結(jié),有幾種層次的實現(xiàn)方法,一是直接得到物理連結(jié),而是通
過傳統(tǒng)的連結(jié)池(沒有多大區(qū)別),三是通過java的擴展包javax.sql.DataSource得到連結(jié)
句柄,對于上面兩種,沒有什么可以多說的,對于DataSource,我們再深入一些.
一般來說,DataSource是容器本身作為一個JDNI查找的對象返回出來的,也就是說要依賴
容器進行配置,而如果一個100%的應(yīng)用程序(Application),比如基于swing的App,我根本
不需要運行容器,那我如何取得DataSource對象?這時可能要你自己寫基于DataSource的
連結(jié)池了(是不是有些太深入了?要做就做高手,SUN能做我們就能做).
如果自己要實現(xiàn)DataSource,要清楚幾個關(guān)系,即DataSource中返回的Connection是一個連
結(jié)的句柄,它要和實際的物理連結(jié)關(guān)連,這些實際的物理連結(jié)就是PooledConnection,我們
叫它池中的連結(jié),可以通過實現(xiàn)ConnectionPoolDataSource,從中得到PooledConnection,
這部分本來是廠商實現(xiàn)的,但這部份實現(xiàn)和傳統(tǒng)的連結(jié)池沒有什么大的區(qū)別,主要是從
ConnectionPoolDataSource中得到PooledConnection的物理連結(jié),但如何從PooledConnection
中g(shù)etConnection(),返回給用戶.這部分實現(xiàn)就是DataSource實現(xiàn)的性能高低的關(guān)鍵,一般
來說,我們可以先把一個物理連結(jié)PooledConnection和多個客戶連結(jié)相關(guān)連來增加性能,也
就是一個PooledConnection本身再作為一個工場的種子,通過一個PooledConnection再返回
多個Connection,說白了就是多個Connection的請求通過一個PooledConnection傳遞給數(shù)據(jù)庫.
只要用戶調(diào)用Connection的close()方法,就打斷這個Connetion與PooledConnection的關(guān)聯(lián)而
讓PooledConnection可以和新的Connection進行關(guān)聯(lián).
JDBC事務(wù)
JDBC1開始,就支持本地事務(wù),所謂要地事務(wù),就是在一個連結(jié)中的多個操作可以作為一個事務(wù)
過程來提交.注意,只要你使用conn.setAutoCommit(false);方法就隱式地打開了一個事務(wù).當(dāng)
事務(wù)被commit或abort時,隱含的是打開了一個新的事務(wù).
另外,當(dāng)一次事務(wù)被commit或abort,PreparedSattement和CallableStatement綁定的結(jié)果集全
部被關(guān)閉,而普通的Statement綁一的結(jié)果集將被維持.
在處理多個操作時:
conn.setAutoCommit(false);
Statement st1 = conn.createSatatement(sql1);
Statement st2 = conn.createSatatement(sql2);
Statement st3 = conn.createSatatement(sql3);
Statement st4 = conn.createSatatement(sql4);
st1.executeXXXXX();
st2.executeXXXXX();
st3.executeXXXXX();
st4.executeXXXXX();
在這里,我們要么把四個操作一起回滾,或一起提交,但如果我們認為,1-3的操作是要求一致完
成,而4的操作在一至三完成時再完成.在經(jīng)前的版本中我們要把它們分開在兩次事務(wù)中,但在
JDBC3.0以后,我們可以利用conn.setSavepoint() 來得到一個SavePoint來對同一事務(wù)中不同階
段進行斷點保存.然后可以在任何斷點上進行提交和回滾.同時我們還可以利用
conn.setTransactionIsolation()來設(shè)置隔離級別.
要注意的是,對事務(wù)的支持要看數(shù)據(jù)庫的具體實現(xiàn).如果數(shù)據(jù)庫本身不支持事務(wù),那么以上的操作
都是無效的,可以從 DatabaseMetaData中查詢數(shù)據(jù)庫對事務(wù)的支持情況.
畢竟,本地事務(wù)功能并不是很強,而如果不是編程人員對SQL語句傳入錯誤,那么在一次連結(jié)中
多個操作只完成部份的機率并不容易發(fā)生(當(dāng)然有時還會發(fā)生的,要不本地事務(wù)就不會產(chǎn)生了).
其實,JDBC事務(wù)最重要的是分布式事務(wù),即同時操作不同的連結(jié),可能是同物理庫的不同空間,也
可能是同一主機的不同數(shù)據(jù)庫或不同主機的多個數(shù)據(jù)庫.這就很難保證每個操作都是成功的,發(fā)
生操作不一致的機會太多了,可以說如果不在事務(wù)中測試你就無法相信操作的一致性.所以分布
式事務(wù)是JDBC的重要技術(shù).
在下一節(jié)我們重點介紹JDBC分布式事務(wù).
JDBC高級應(yīng)用(二)
本來想繼續(xù)談JDBC的高級連結(jié)方式,事務(wù)模式.但發(fā)現(xiàn)關(guān)于大對象存儲有很多人在問,所以
先來插入一節(jié)關(guān)于大對象存儲的內(nèi)容,然后再接著原來的思路寫下去.
JDBC的大對象存儲聽起來復(fù)雜,其實如果你明白了原理以后,就非常簡單,網(wǎng)上有關(guān)這方面的
教材很少,而SUN的文檔中,我從1.2開始看到一在仍然是錯誤的,不知道寫文檔的人長腦子沒
有,就那幾行代碼你試試不就知道了,這么多次重抄下來還是錯誤的.
大對象分類:一般來說,大對象分為:大的文本對象,比如一個很長的文本(請你要注意什么是
文本文件,什么是二進制文件)文件,或者是你定義的一個長字符串,比如你定義了:
String s = "我們要去吃飯了......................然后睡覺!";
從吃飯到睡覺中間省略了實際的10000000000000字,雖然你不會真的定義這么稱的String,但
有時會從什么地方得到這樣的String,要寫到數(shù)據(jù)庫中.
另一種就是大的二進制對象,象執(zhí)行文件,圖象文件等,注意,word,excel,ppt這些"帶格式"的文
檔都應(yīng)該以二進制對象存儲.
一般來說,數(shù)據(jù)庫如果支持大對象存儲,會有這幾種類型的SQL數(shù)據(jù)類型:
BLOB,CLOCB,NLOB,也有的數(shù)據(jù)數(shù)只有一種BLOB,基本上是這樣的:BLOB用來存放二進制文件,而
CLOB用來存放文本文件,NLOB是對多字節(jié)文本文件支持.假如你的文本文件是純英文的,放在
BLOB中當(dāng)然可以,也就是說它是以byte格式存儲的,而多字節(jié)是以CHAR格式存儲的.
同樣對于這幾種類型的文檔,有幾種相對應(yīng)的存取方式:
setter:
利用PreparedStatement的setXXX方法,
setAsciiStream()方法用于寫入一般的文本流.setBinaryStream()方法用于寫入二進制流
而setUnicodeStream()用于寫好UNICODE編碼的文本,與此相對應(yīng)的ResultSet中三個getter方法
用于取回:getAsciiStream(),getBinaryStream(),getBinaryStream().
對于文件本身,要把它作為一個流,只要new InputStream(new FileInputStream("文件路徑"))
就可以了,但對于大的String對象,你不會寫入文件再轉(zhuǎn)換成輸入流吧?
new StringBufferInputStream(String s),記住了.
JDBC2以后提供了java.sql.BLOB對象,我不建議大家使用它,一是很麻類,二是容易出錯,要先插
入一個空的BLOB對象,然后再填充它,實在沒有必要,直接setXXX就行了,我試過,至少mysql,
oracle,sql server是可以直接set的.
好了,我們先看一個例子如何寫入文件到數(shù)據(jù)庫:
數(shù)據(jù)結(jié)構(gòu):
create table test(
name varchar(200),
content BLOB
);
File f = new File("a.exe");//先生成File對象是為了取得流的長度.FileInputStram可以直接
//傳入文件路徑
InputStream in = new InputStream(new FileInputStream(f));
PreparedStatement ps = conn.prepareStatement("insert into test (?,?)");
ps.setString(1,"a.exe");
ps.setBinaryStream(2,in,(int)f.length());
ps.executeUpdate();
f的長度一定要做從long到int的轉(zhuǎn)換,SUN的文檔中好幾版都沒有改過來.就這么簡單,當(dāng)然,不同的
數(shù)據(jù)庫存本身要設(shè)置它允許的最大長度,MYSQL默認只能傳1M的文件,要修改參數(shù)原能存更大的文件.
如果要從數(shù)庫中取得文件:
PreparedStatement ps = conn.prepareStatement("select * from test where name=?");
ps.setString(1,"a.exe");
ResultSet rs = ps.executeQuery();
if(rs.next()){
InputStream in = rs.getBinaryStream("content");
}
得到in對象后,你可以進行任何處理,寫向文件和寫向頁面只是out對象不同而已:
寫向文件:
DateOutputStream out = new DateOutputStream(new FileOutputStream("b.exe"));
寫向頁面:
response.reset();
response.setContType("類型");
ServletOutputSreamt out = response.getOutputSream();
得到out對象后,就可以輸出了:
byte[] buf = new byte[1024];
int len = 0;
while((len = in.read(buf)) >0)
out.write(buf,0,len);
in.close();
out.close();
對于向頁面輸入,要設(shè)置什么樣的ContType,要看你想如何輸出,如果你想讓對方下載,就設(shè)為
"application/octet-stream",這樣即使是文本,圖象都會下載而不會在瀏覽器中打開.如果你要想
在瀏覽器中打開,就要設(shè)置相應(yīng)的類型,還要在容器的配置文件中設(shè)置支持這種文檔類型的輸出,但
對于很多格式的文件,到底要輸出什么類型,其實就是HTTP的MIME集,比如圖片:image/gif,當(dāng)然你如
果你的文件擴展名(ext)不確定,你也不要用if(ext.equals("gif"))......這樣來判斷,我教你一個
技巧,我之所以說是技巧,是我沒有在別的地方發(fā)現(xiàn)有人用這種方法,對我來說我是絕對不會把別人的
方法拿來說是我的技巧的:
構(gòu)造一個file類型的URL,我們知道URL目前JAVA可以支持HTTP,FTP,MAILTO,FILE,LDAP等,從FILE類型
的URL就可以得到它的MIME:
URL u = new URL("file://a.exe");
String mime = u.openConnection().getContentType();
這樣你就可以直接response.setContType(mime);而不用一個一個類型判斷了.
好了,大對象存儲就說到這兒,不同的數(shù)據(jù)仍然和些特殊的規(guī)定,不在此一一列舉了.
本來想繼續(xù)談JDBC的高級連結(jié)方式,事務(wù)模式.但發(fā)現(xiàn)關(guān)于大對象存儲有很多人在問,所以
先來插入一節(jié)關(guān)于大對象存儲的內(nèi)容,然后再接著原來的思路寫下去.
JDBC的大對象存儲聽起來復(fù)雜,其實如果你明白了原理以后,就非常簡單,網(wǎng)上有關(guān)這方面的
教材很少,而SUN的文檔中,我從1.2開始看到一在仍然是錯誤的,不知道寫文檔的人長腦子沒
有,就那幾行代碼你試試不就知道了,這么多次重抄下來還是錯誤的.
大對象分類:一般來說,大對象分為:大的文本對象,比如一個很長的文本(請你要注意什么是
文本文件,什么是二進制文件)文件,或者是你定義的一個長字符串,比如你定義了:
String s = "我們要去吃飯了......................然后睡覺!";
從吃飯到睡覺中間省略了實際的10000000000000字,雖然你不會真的定義這么稱的String,但
有時會從什么地方得到這樣的String,要寫到數(shù)據(jù)庫中.
另一種就是大的二進制對象,象執(zhí)行文件,圖象文件等,注意,word,excel,ppt這些"帶格式"的文
檔都應(yīng)該以二進制對象存儲.
一般來說,數(shù)據(jù)庫如果支持大對象存儲,會有這幾種類型的SQL數(shù)據(jù)類型:
BLOB,CLOCB,NLOB,也有的數(shù)據(jù)數(shù)只有一種BLOB,基本上是這樣的:BLOB用來存放二進制文件,而
CLOB用來存放文本文件,NLOB是對多字節(jié)文本文件支持.假如你的文本文件是純英文的,放在
BLOB中當(dāng)然可以,也就是說它是以byte格式存儲的,而多字節(jié)是以CHAR格式存儲的.
同樣對于這幾種類型的文檔,有幾種相對應(yīng)的存取方式:
setter:
利用PreparedStatement的setXXX方法,
setAsciiStream()方法用于寫入一般的文本流.setBinaryStream()方法用于寫入二進制流
而setUnicodeStream()用于寫好UNICODE編碼的文本,與此相對應(yīng)的ResultSet中三個getter方法
用于取回:getAsciiStream(),getBinaryStream(),getBinaryStream().
對于文件本身,要把它作為一個流,只要new InputStream(new FileInputStream("文件路徑"))
就可以了,但對于大的String對象,你不會寫入文件再轉(zhuǎn)換成輸入流吧?
new StringBufferInputStream(String s),記住了.
JDBC2以后提供了java.sql.BLOB對象,我不建議大家使用它,一是很麻類,二是容易出錯,要先插
入一個空的BLOB對象,然后再填充它,實在沒有必要,直接setXXX就行了,我試過,至少mysql,
oracle,sql server是可以直接set的.
好了,我們先看一個例子如何寫入文件到數(shù)據(jù)庫:
數(shù)據(jù)結(jié)構(gòu):
create table test(
name varchar(200),
content BLOB
);
File f = new File("a.exe");//先生成File對象是為了取得流的長度.FileInputStram可以直接
//傳入文件路徑
InputStream in = new InputStream(new FileInputStream(f));
PreparedStatement ps = conn.prepareStatement("insert into test (?,?)");
ps.setString(1,"a.exe");
ps.setBinaryStream(2,in,(int)f.length());
ps.executeUpdate();
f的長度一定要做從long到int的轉(zhuǎn)換,SUN的文檔中好幾版都沒有改過來.就這么簡單,當(dāng)然,不同的
數(shù)據(jù)庫存本身要設(shè)置它允許的最大長度,MYSQL默認只能傳1M的文件,要修改參數(shù)原能存更大的文件.
如果要從數(shù)庫中取得文件:
PreparedStatement ps = conn.prepareStatement("select * from test where name=?");
ps.setString(1,"a.exe");
ResultSet rs = ps.executeQuery();
if(rs.next()){
InputStream in = rs.getBinaryStream("content");
}
得到in對象后,你可以進行任何處理,寫向文件和寫向頁面只是out對象不同而已:
寫向文件:
DateOutputStream out = new DateOutputStream(new FileOutputStream("b.exe"));
寫向頁面:
response.reset();
response.setContType("類型");
ServletOutputSreamt out = response.getOutputSream();
得到out對象后,就可以輸出了:
byte[] buf = new byte[1024];
int len = 0;
while((len = in.read(buf)) >0)
out.write(buf,0,len);
in.close();
out.close();
對于向頁面輸入,要設(shè)置什么樣的ContType,要看你想如何輸出,如果你想讓對方下載,就設(shè)為
"application/octet-stream",這樣即使是文本,圖象都會下載而不會在瀏覽器中打開.如果你要想
在瀏覽器中打開,就要設(shè)置相應(yīng)的類型,還要在容器的配置文件中設(shè)置支持這種文檔類型的輸出,但
對于很多格式的文件,到底要輸出什么類型,其實就是HTTP的MIME集,比如圖片:image/gif,當(dāng)然你如
果你的文件擴展名(ext)不確定,你也不要用if(ext.equals("gif"))......這樣來判斷,我教你一個
技巧,我之所以說是技巧,是我沒有在別的地方發(fā)現(xiàn)有人用這種方法,對我來說我是絕對不會把別人的
方法拿來說是我的技巧的:
構(gòu)造一個file類型的URL,我們知道URL目前JAVA可以支持HTTP,FTP,MAILTO,FILE,LDAP等,從FILE類型
的URL就可以得到它的MIME:
URL u = new URL("file://a.exe");
String mime = u.openConnection().getContentType();
這樣你就可以直接response.setContType(mime);而不用一個一個類型判斷了.
好了,大對象存儲就說到這兒,不同的數(shù)據(jù)仍然和些特殊的規(guī)定,不在此一一列舉了.
JDBC高級應(yīng)用 一
關(guān)于數(shù)據(jù)庫連結(jié)
我們所說有JDBC高級應(yīng)用,并不是說它的技術(shù)含量很高(也許JAVA平臺上不存在什么"技術(shù)含量"的說
法,因為JAVA是給大家用的而不是給某些人用的).說它是高級應(yīng)用,是因為它是對于JDBC基礎(chǔ)應(yīng)用來
說的擴展,也就是可以優(yōu)化你的應(yīng)用性能,或方便于應(yīng)用的實現(xiàn).所以說它是一種高級應(yīng)用而不叫高級
技術(shù).
JDBC中,java.sql包是基礎(chǔ)的,也是核心的功能,javax.sql包則是高級的,擴展的功能.所以
為了交流的方便,我們把它們區(qū)分為core API和optional API.
但是仍然然有一些core API中的功能,我把它歸納到高級應(yīng)用中.象存儲過程的調(diào)用,多結(jié)果集
的處理,我之所以要把這些東西拿出來說明,是目前你在網(wǎng)上找不到任何一份詳細的文檔和例程,可以
說有95%以上的開發(fā)人員都不知道真正如何處理這些工作.所以我會在回上海后詳細寫這一段的內(nèi)容.
現(xiàn)在我們還是來看看optional API給我們帶來的好處:
我們已經(jīng)了解,執(zhí)行一個SQL語句,要經(jīng)過如下幾步:
1.Connction
2.Statement
3.Statement.executeXXXXX();
4.可選的對結(jié)果集的處理
5.必要的Connction的關(guān)閉.(再次提醒如果你想成為中級水平以上的程序員,請你把關(guān)閉語
句寫在finally塊中,在通過下面的介紹后我介紹一個方法可以用來驗證你的程序是否有連結(jié)
泄漏)
這其中,生成Connction對象是最最重要的工作,也是最消耗資源的,因為連結(jié)對象要驅(qū)動底層
的SOCKET,調(diào)用物理連結(jié)和數(shù)據(jù)庫進行通信,所以生成,關(guān)閉,再生成這種連結(jié)對象就相當(dāng)于我們在二十
世紀八十年代(1980年以后出身的不了解吧?)喝易拉罐飲料一樣.你買一瓶飲料是一塊二角錢,你可知道
那罐子(Connection)值一塊零八分.而你喝下去的東西只值一角二分錢,這是我們那兒一個飲料廠的真實
數(shù)據(jù).
在javax.sql包出來以前,我們只能買這樣的飲料來喝,除非你不喝.也有一些人不服氣自己生
產(chǎn)飲料(poolman),可是消費者很快發(fā)現(xiàn),它只是把原來單賣的易拉罐現(xiàn)在打包賣給了我們,因為它還是
用原來的包裝原料來生產(chǎn)的,poolman這種類型的連結(jié)池,其根本是從DriverManager中g(shù)etConnection
出來的.真正的效率如果不是你心理作用的話,也許比單個連結(jié)還要低!!!以及一些江湖好漢的"杰作",
都無法跳出這個框框.我自己在那一段時間也曾醉心于研究這些"連結(jié)池",因為誰都可以把別人的原碼
讀過后,再加上其他人的優(yōu)點寫出一個更好的來,可是,大家可以看到,我寫出了好用的Upload,DownLoad,
HtmlUtil,Encoder,Decoder等一系列工具.可是我沒有寫出成功的連結(jié)池.......
我們再來深入一步,為什么DriverManager生成的連結(jié)和基于它的連結(jié)池不能真正提高性能.
DriverManager對象中,絕大多數(shù)的JDBC是封裝了一個物理連結(jié),也就是它抓住了一個和數(shù)據(jù)庫通信的
Socket,當(dāng)你使用DriverManager.getConnection()時也就是有一個和數(shù)據(jù)庫連結(jié)的Socket讓你占用了.
而且這個方法是同步的.大家知道這樣的物理連結(jié)對于任何系統(tǒng)是有限制的,比如一個WEB服務(wù)器一般
最大并發(fā)是150到250之間,數(shù)據(jù)庫服務(wù)器也是這樣的道理,你不僅要考慮你的程序不能用光連結(jié),還要
考慮不同Runtime中或其它應(yīng)用程序也在同時和你一起使用這些物理連結(jié),如果一臺服務(wù)器上有JAVA
WEB SERVER,還有一個C的應(yīng)用程序也訪問數(shù)據(jù)庫,你不能那么無禮地要求人家C程序員他的程序必須
等你的JAVA調(diào)用空閑才能訪問數(shù)據(jù)庫吧.所以物理連結(jié)是極其寶貴的.
基于DriverManager.getConnection()的連結(jié)池只不過是預(yù)先生成這樣的物理連結(jié)放在一個
pool中,然后編號等你調(diào)用,它省略的是生成這樣的連結(jié)的時間.注意你得到的連結(jié)在你沒有釋放之前,
它無法處理別的工作,因為連結(jié)句柄在用戶手中,另外這種連結(jié)池調(diào)用時是由程序調(diào)用者初始化的,
每一次調(diào)用都必須有初始化工作,而調(diào)用者是否以優(yōu)化的方法去運行它,完成還要看每個人的編程水
平.另一方面,如果這種連結(jié)池是如果用于WEB容器管理,那簡單是垃圾,因為它強迫使用靜態(tài)變量來
保持連結(jié),容器根本無法做到訪問控制.而且它不能在不同Runtime中被調(diào)用.
而javax.sql的實現(xiàn)采用了在用戶連結(jié)和物理連結(jié)中間加一個緩沖的中間層,它雖然也只生
成30個物理連結(jié),但用戶本身不能訪問它,DataSource返回給用戶的是一個JAVA抽象對象,客戶程序把
連結(jié)請求放回緩沖中由DataSource統(tǒng)一調(diào)度物理連結(jié)來處理,這樣可以最大程序利用寶貴的物理連結(jié)
也許現(xiàn)在的30個物理連結(jié)仍然不夠負載,你仍然需要修改實際連結(jié)數(shù),但我們知道,我們現(xiàn)在的這種連
結(jié)方式已經(jīng)不是DriverManager.getConnection()能比的了.也就是說,在同樣多的物理連結(jié)下,
DataSource可以給我們更多(是多得多的)的調(diào)用機會,其實,正常情況下一個從DriverManager中
getConnection()出來物理連結(jié)的負載量只有百分之幾,就是因為你的調(diào)用抓住了它的句柄而不能讓
它很好地工作.另外程序員只能返回它而不能關(guān)閉它,因為傳統(tǒng)連結(jié)池中連結(jié)對象一旦由用戶關(guān)閉,
就要再次重新生成物理新的連結(jié),所以用戶只能釋放,對于非連結(jié)池和連結(jié)池得到的連結(jié)對象,
要用不同的代碼編程,簡單是一種痛苦.一旦沒有注意,在處理完數(shù)據(jù)后不是釋放而是關(guān)閉,這個錯誤
到底是誰的過錯?????????????
我上面說java.sql實現(xiàn)絕大多數(shù)是得到物理連結(jié),也有例外,但不是那些以前的連結(jié)池,而是
OSE,就是oracle的servlet環(huán)境,它是把servlet服務(wù)器實現(xiàn)在數(shù)據(jù)庫的地址空間上,servlet服務(wù)去調(diào)
用數(shù)據(jù)庫根本沒有通過傳統(tǒng)的連結(jié),因為數(shù)據(jù)是"敞開"的,這就象通過網(wǎng)絡(luò)訪問其它計算機文件和訪問
本地文件的區(qū)別. 雖然它也提供標準的JDBC接口讓你調(diào)用,但它底層根本不是用JDBC封裝的.
DataSource的另外一個優(yōu)點就是它完全實現(xiàn)數(shù)據(jù)庫和應(yīng)用程序的分離,如何配置服務(wù)器生成
DataSource的引用和程序開發(fā)無關(guān),你在程序開發(fā)中,只要通過JDNI查找DataSource的邏輯名稱就行.
而DriverManager.getConnection()中,你不得不把數(shù)據(jù)庫驅(qū)動程序名,訪問地址,用戶,密碼寫在你的
應(yīng)用程序中,即使可以從配置文件中讀取這些屬性串,而不同的服務(wù)器配置文件的路徑你都要修改.而
DataSource提供了標準的配置.
說來說去,如何用DataSource來連結(jié)數(shù)據(jù)庫?
非常簡單:
DataSource ds = (DataSource)new InitialContext().lookup("jdbc/mydb");
Connection conn = ds.getConnection();
當(dāng)然我是為了說明它簡單故意把它的異常給忽略了.實際應(yīng)用中.你應(yīng)該捕獲它的異常.
如果你還不明白什么是JDNI,我勸你先找一些這方面的資料看看,這可以是網(wǎng)編程方面的
基礎(chǔ)協(xié)議啊.
關(guān)于DataSource ds = (DataSource)new InitialContext().lookup("jdbc/mydb");有幾點
需要說明的是,你只要配置好你的服務(wù)器(tomcat,resin,weblogic)這些服務(wù)器中都有一個例子,如果
你不是很懂,你先把那個例子改動幾個字就行了.用一段時間你就會慢慢理解它們代表什么了.
然后你在容器環(huán)境下調(diào)用new InitialContext().lookup("jdbc/mydb")容器就會自動找到那個
DataSource對象給你調(diào)用,這個過程對用戶來說是透明的.
那邊那個聰明的朋友已經(jīng)問了,因為DataSource的屬性是已經(jīng)配置好的放在容器中的,那我
不在容器環(huán)境下,比如一個獨立的application,我如何能取到DataSource呢?
其實,如果你能知道new InitialContext()時,容器調(diào)用了哪些默認的配置,你就可以把
這些配置參數(shù)手工加進去而不依賴容器環(huán)境了.好在InitialContext可以getEnvironment() ,在生
成這個對象后你可以get一下看看,把這些參數(shù)記下來,以后在沒有這些參數(shù)的環(huán)境下put進去.
這里多幾句話,談一下學(xué)習(xí)方法,我在國內(nèi)主持幾個論壇(不多,兩三個),從沒有問過別人什
么問題,java技術(shù)又不是我發(fā)明的,不可能我什么都懂,一是問了好象有損于"高手"風(fēng)范(哈哈,其實
真正的高手還是要問別人的,只有我這種假高手才不會問別人).另一方面是我根本不必問別人,比如
象application下,連結(jié)不同廠家的DataSource要put什么東西進去呢?一是去他們的網(wǎng)站看資料,雖然
我的英語水平只有大家的10%,但我上英文網(wǎng)站的次數(shù)可能比你們多.二是看有沒有什么共用的API能
得到,好在可以getEnvironment(),但假如沒有這個方法呢?這就要看你的學(xué)習(xí)態(tài)度了,有人會這論壇
叫"高手球命",還有什么"急用,在線等待"什么的.而我,會把new InitialContext()反編譯出來看看
它調(diào)用了什么(孔子說,為了學(xué)習(xí)的目的,反編譯是允許的,甚至說是偉大的,光明的,正確的思想,是有
道德的,脫離了低級趣味的,有益于人民的行為!!!----孔子語錄補集第123頁第4行,1989年10月版)
如果一個對象在構(gòu)造時要求有參數(shù),而它又有一個沒有參數(shù)的重載的構(gòu)造方法,你想想它肯定在沒有
參數(shù)的構(gòu)造方法中調(diào)用了默認參數(shù),你要做的就是把它們打印出來.有多少人是這樣做的?
jdbc optional API的其它擴展功能:
javax.sql不僅僅是在性能上的提高,而且它還支持分布式事務(wù),在傳統(tǒng)的連結(jié)過程中,我們
可以在一個連結(jié)過程中,setAutoCommit()為fasle,然后通過rollback()或commit()那回滾和提交事
務(wù),這種在一個連結(jié)過程中的事務(wù)稱為本地事務(wù),但假如在一個事務(wù)中要對多個數(shù)據(jù)庫操作,或多過
Servlet參與操作,那就必須使用分布式事務(wù).
關(guān)于JDBC的事務(wù)我會放在下面來介紹,一個值得慶賀的功能出來了,就是事務(wù)保存點已經(jīng)
JDBC3.0中實現(xiàn),以前,如果我們把事務(wù)原子A,B,C放在一個事務(wù)中,如果A,B執(zhí)行了,C失敗,我們只能
把A,B都回滾了,但現(xiàn)在我們可以先把A,B保存為一個點,然后以這個點為回滾或提交,這就象在用WORD
編寫文章時我們可以在不同的時候保存一個副本,而不會要么一字沒有了,要么就是當(dāng)前編輯的狀態(tài).
現(xiàn)在我們來優(yōu)化我們在基礎(chǔ)知識中實現(xiàn)的Bean,今天在家,沒法上論壇,上次寫的連結(jié)部分
叫什么名字忘記了,現(xiàn)在我們就叫它PooledDB吧.
當(dāng)時我們已經(jīng)把那個Bean分為三個部分,把生成連結(jié)部分獨立出來了,而業(yè)務(wù)方法部份和擴
展部分根本不要動它,這就是繼承的好處:)
package com.inmsg.beans;
import javax.naming.*;
import javax.sql.*;
public class PooledDB {
Connection con = null;
private String source = "";
public PooledDB() throws Exception {//默認構(gòu)造方法,如果構(gòu)造時不加參數(shù),連結(jié)jdbc/office
source = "java:comp/env/jdbc/office";
Context ct = new InitialContext();
DataSource ds = (DataSource) ct.lookup(source);
con = ds.getConnection();
}
//然后增加重載方法,用來連結(jié)其它的數(shù)據(jù)源
public PooledDB(String source) throws Exception {
this.source = source;
Context ct = new InitialContext();
DataSource ds = (DataSource) ct.lookup(source);
con = ds.getConnection();
}
//注意一定要先把source賦給成員變量this.source,因為下面還有一個makeConnection()
//輔助方法,如果不把source賦給this.source,則makeConnection()調(diào)用默認的source字符串
private void makeConnection() throws Exception {
Context ct = new InitialContext();
DataSource ds = (DataSource) ct.lookup(source);
con = ds.getConnection();
}
//現(xiàn)在我們把close()方法拿到父類來實現(xiàn),這是經(jīng)過綜合考慮的,它是一個業(yè)務(wù)方法,無論是什么
//方式取得連結(jié),它本身不會修改,但為什么還封裝到父類中呢?因為這樣可以用一個獨立的父類來
//做連結(jié)測試,如果我只想試一下數(shù)據(jù)庫能不能連結(jié),我就不必再引用子類,直接用這個父類就行了
public void close() throws Exception{
if(con != null && !con.isClosed()) con.close();
}
}
一般來說,構(gòu)造方法盡量捕獲異常處理而不要拋出異常,但作為Bean的實現(xiàn),捕獲異常調(diào)用者不容易
看到異常信息,所以拋給調(diào)用者處理,另外這個類又要在應(yīng)用程序中調(diào)用,又要考慮作為Bean調(diào)用,所以一定
要有一個無參數(shù)的構(gòu)造方法,否則不能作為javaBean調(diào)用.把異常拋出給調(diào)用者的另一目的,我在設(shè)計時是這
樣考慮的,就是強迫你一定在使用try{}catch(){}塊,這樣你就會想到再加一個finally{},再次提醒,一定要
以下面的形式來調(diào)用你數(shù)據(jù)庫連結(jié)的Bean或封裝類:
PooledDB pd = null;
try{
pd = new PooledDB();
....................
}
catch(Exception e){}
finally{try{pd.close();}catch(Exception ex){}}
如果要測試你的數(shù)據(jù)庫連結(jié)是否有泄漏,請你把DataSource中最大連結(jié)數(shù)設(shè)為1,只用一個連結(jié)的情
況下,如果你的程序中哪一處沒有關(guān)閉連結(jié),則下面的程序就不能再訪問,然后從頭到尾測試你的程序吧,一旦
發(fā)現(xiàn)不能訪問數(shù)據(jù)庫了,就查看剛才訪問的代碼,這樣所有程序測試后,就可以放心了,一般來說我是不用這么
測試的,因為我在寫數(shù)據(jù)庫連結(jié)時是沒有生成對象就寫好close:
PooledDB pd = null;
try{}
catch(Exception e){}
finally{try{pd.close();}catch(Exception ex){}}
然后原在try{}的花括號中回車加上pd = PooledDB();和業(yè)務(wù)代碼的 :)當(dāng)然,你們都比我聰明用不
著這樣死套也不會忘記close()的.
不要太高興,到目前為止你仍然還沒得到一個最好的解決方案,以下我們還會對這個數(shù)據(jù)庫連結(jié)的
Bean(類)不斷優(yōu)化的..................................
好了,javax.sql的連結(jié)先說到這兒吧,今天是周六,出去玩一會了(廣州街頭上沒有什么美女!)
關(guān)于數(shù)據(jù)庫連結(jié)
我們所說有JDBC高級應(yīng)用,并不是說它的技術(shù)含量很高(也許JAVA平臺上不存在什么"技術(shù)含量"的說
法,因為JAVA是給大家用的而不是給某些人用的).說它是高級應(yīng)用,是因為它是對于JDBC基礎(chǔ)應(yīng)用來
說的擴展,也就是可以優(yōu)化你的應(yīng)用性能,或方便于應(yīng)用的實現(xiàn).所以說它是一種高級應(yīng)用而不叫高級
技術(shù).
JDBC中,java.sql包是基礎(chǔ)的,也是核心的功能,javax.sql包則是高級的,擴展的功能.所以
為了交流的方便,我們把它們區(qū)分為core API和optional API.
但是仍然然有一些core API中的功能,我把它歸納到高級應(yīng)用中.象存儲過程的調(diào)用,多結(jié)果集
的處理,我之所以要把這些東西拿出來說明,是目前你在網(wǎng)上找不到任何一份詳細的文檔和例程,可以
說有95%以上的開發(fā)人員都不知道真正如何處理這些工作.所以我會在回上海后詳細寫這一段的內(nèi)容.
現(xiàn)在我們還是來看看optional API給我們帶來的好處:
我們已經(jīng)了解,執(zhí)行一個SQL語句,要經(jīng)過如下幾步:
1.Connction
2.Statement
3.Statement.executeXXXXX();
4.可選的對結(jié)果集的處理
5.必要的Connction的關(guān)閉.(再次提醒如果你想成為中級水平以上的程序員,請你把關(guān)閉語
句寫在finally塊中,在通過下面的介紹后我介紹一個方法可以用來驗證你的程序是否有連結(jié)
泄漏)
這其中,生成Connction對象是最最重要的工作,也是最消耗資源的,因為連結(jié)對象要驅(qū)動底層
的SOCKET,調(diào)用物理連結(jié)和數(shù)據(jù)庫進行通信,所以生成,關(guān)閉,再生成這種連結(jié)對象就相當(dāng)于我們在二十
世紀八十年代(1980年以后出身的不了解吧?)喝易拉罐飲料一樣.你買一瓶飲料是一塊二角錢,你可知道
那罐子(Connection)值一塊零八分.而你喝下去的東西只值一角二分錢,這是我們那兒一個飲料廠的真實
數(shù)據(jù).
在javax.sql包出來以前,我們只能買這樣的飲料來喝,除非你不喝.也有一些人不服氣自己生
產(chǎn)飲料(poolman),可是消費者很快發(fā)現(xiàn),它只是把原來單賣的易拉罐現(xiàn)在打包賣給了我們,因為它還是
用原來的包裝原料來生產(chǎn)的,poolman這種類型的連結(jié)池,其根本是從DriverManager中g(shù)etConnection
出來的.真正的效率如果不是你心理作用的話,也許比單個連結(jié)還要低!!!以及一些江湖好漢的"杰作",
都無法跳出這個框框.我自己在那一段時間也曾醉心于研究這些"連結(jié)池",因為誰都可以把別人的原碼
讀過后,再加上其他人的優(yōu)點寫出一個更好的來,可是,大家可以看到,我寫出了好用的Upload,DownLoad,
HtmlUtil,Encoder,Decoder等一系列工具.可是我沒有寫出成功的連結(jié)池.......
我們再來深入一步,為什么DriverManager生成的連結(jié)和基于它的連結(jié)池不能真正提高性能.
DriverManager對象中,絕大多數(shù)的JDBC是封裝了一個物理連結(jié),也就是它抓住了一個和數(shù)據(jù)庫通信的
Socket,當(dāng)你使用DriverManager.getConnection()時也就是有一個和數(shù)據(jù)庫連結(jié)的Socket讓你占用了.
而且這個方法是同步的.大家知道這樣的物理連結(jié)對于任何系統(tǒng)是有限制的,比如一個WEB服務(wù)器一般
最大并發(fā)是150到250之間,數(shù)據(jù)庫服務(wù)器也是這樣的道理,你不僅要考慮你的程序不能用光連結(jié),還要
考慮不同Runtime中或其它應(yīng)用程序也在同時和你一起使用這些物理連結(jié),如果一臺服務(wù)器上有JAVA
WEB SERVER,還有一個C的應(yīng)用程序也訪問數(shù)據(jù)庫,你不能那么無禮地要求人家C程序員他的程序必須
等你的JAVA調(diào)用空閑才能訪問數(shù)據(jù)庫吧.所以物理連結(jié)是極其寶貴的.
基于DriverManager.getConnection()的連結(jié)池只不過是預(yù)先生成這樣的物理連結(jié)放在一個
pool中,然后編號等你調(diào)用,它省略的是生成這樣的連結(jié)的時間.注意你得到的連結(jié)在你沒有釋放之前,
它無法處理別的工作,因為連結(jié)句柄在用戶手中,另外這種連結(jié)池調(diào)用時是由程序調(diào)用者初始化的,
每一次調(diào)用都必須有初始化工作,而調(diào)用者是否以優(yōu)化的方法去運行它,完成還要看每個人的編程水
平.另一方面,如果這種連結(jié)池是如果用于WEB容器管理,那簡單是垃圾,因為它強迫使用靜態(tài)變量來
保持連結(jié),容器根本無法做到訪問控制.而且它不能在不同Runtime中被調(diào)用.
而javax.sql的實現(xiàn)采用了在用戶連結(jié)和物理連結(jié)中間加一個緩沖的中間層,它雖然也只生
成30個物理連結(jié),但用戶本身不能訪問它,DataSource返回給用戶的是一個JAVA抽象對象,客戶程序把
連結(jié)請求放回緩沖中由DataSource統(tǒng)一調(diào)度物理連結(jié)來處理,這樣可以最大程序利用寶貴的物理連結(jié)
也許現(xiàn)在的30個物理連結(jié)仍然不夠負載,你仍然需要修改實際連結(jié)數(shù),但我們知道,我們現(xiàn)在的這種連
結(jié)方式已經(jīng)不是DriverManager.getConnection()能比的了.也就是說,在同樣多的物理連結(jié)下,
DataSource可以給我們更多(是多得多的)的調(diào)用機會,其實,正常情況下一個從DriverManager中
getConnection()出來物理連結(jié)的負載量只有百分之幾,就是因為你的調(diào)用抓住了它的句柄而不能讓
它很好地工作.另外程序員只能返回它而不能關(guān)閉它,因為傳統(tǒng)連結(jié)池中連結(jié)對象一旦由用戶關(guān)閉,
就要再次重新生成物理新的連結(jié),所以用戶只能釋放,對于非連結(jié)池和連結(jié)池得到的連結(jié)對象,
要用不同的代碼編程,簡單是一種痛苦.一旦沒有注意,在處理完數(shù)據(jù)后不是釋放而是關(guān)閉,這個錯誤
到底是誰的過錯?????????????
我上面說java.sql實現(xiàn)絕大多數(shù)是得到物理連結(jié),也有例外,但不是那些以前的連結(jié)池,而是
OSE,就是oracle的servlet環(huán)境,它是把servlet服務(wù)器實現(xiàn)在數(shù)據(jù)庫的地址空間上,servlet服務(wù)去調(diào)
用數(shù)據(jù)庫根本沒有通過傳統(tǒng)的連結(jié),因為數(shù)據(jù)是"敞開"的,這就象通過網(wǎng)絡(luò)訪問其它計算機文件和訪問
本地文件的區(qū)別. 雖然它也提供標準的JDBC接口讓你調(diào)用,但它底層根本不是用JDBC封裝的.
DataSource的另外一個優(yōu)點就是它完全實現(xiàn)數(shù)據(jù)庫和應(yīng)用程序的分離,如何配置服務(wù)器生成
DataSource的引用和程序開發(fā)無關(guān),你在程序開發(fā)中,只要通過JDNI查找DataSource的邏輯名稱就行.
而DriverManager.getConnection()中,你不得不把數(shù)據(jù)庫驅(qū)動程序名,訪問地址,用戶,密碼寫在你的
應(yīng)用程序中,即使可以從配置文件中讀取這些屬性串,而不同的服務(wù)器配置文件的路徑你都要修改.而
DataSource提供了標準的配置.
說來說去,如何用DataSource來連結(jié)數(shù)據(jù)庫?
非常簡單:
DataSource ds = (DataSource)new InitialContext().lookup("jdbc/mydb");
Connection conn = ds.getConnection();
當(dāng)然我是為了說明它簡單故意把它的異常給忽略了.實際應(yīng)用中.你應(yīng)該捕獲它的異常.
如果你還不明白什么是JDNI,我勸你先找一些這方面的資料看看,這可以是網(wǎng)編程方面的
基礎(chǔ)協(xié)議啊.
關(guān)于DataSource ds = (DataSource)new InitialContext().lookup("jdbc/mydb");有幾點
需要說明的是,你只要配置好你的服務(wù)器(tomcat,resin,weblogic)這些服務(wù)器中都有一個例子,如果
你不是很懂,你先把那個例子改動幾個字就行了.用一段時間你就會慢慢理解它們代表什么了.
然后你在容器環(huán)境下調(diào)用new InitialContext().lookup("jdbc/mydb")容器就會自動找到那個
DataSource對象給你調(diào)用,這個過程對用戶來說是透明的.
那邊那個聰明的朋友已經(jīng)問了,因為DataSource的屬性是已經(jīng)配置好的放在容器中的,那我
不在容器環(huán)境下,比如一個獨立的application,我如何能取到DataSource呢?
其實,如果你能知道new InitialContext()時,容器調(diào)用了哪些默認的配置,你就可以把
這些配置參數(shù)手工加進去而不依賴容器環(huán)境了.好在InitialContext可以getEnvironment() ,在生
成這個對象后你可以get一下看看,把這些參數(shù)記下來,以后在沒有這些參數(shù)的環(huán)境下put進去.
這里多幾句話,談一下學(xué)習(xí)方法,我在國內(nèi)主持幾個論壇(不多,兩三個),從沒有問過別人什
么問題,java技術(shù)又不是我發(fā)明的,不可能我什么都懂,一是問了好象有損于"高手"風(fēng)范(哈哈,其實
真正的高手還是要問別人的,只有我這種假高手才不會問別人).另一方面是我根本不必問別人,比如
象application下,連結(jié)不同廠家的DataSource要put什么東西進去呢?一是去他們的網(wǎng)站看資料,雖然
我的英語水平只有大家的10%,但我上英文網(wǎng)站的次數(shù)可能比你們多.二是看有沒有什么共用的API能
得到,好在可以getEnvironment(),但假如沒有這個方法呢?這就要看你的學(xué)習(xí)態(tài)度了,有人會這論壇
叫"高手球命",還有什么"急用,在線等待"什么的.而我,會把new InitialContext()反編譯出來看看
它調(diào)用了什么(孔子說,為了學(xué)習(xí)的目的,反編譯是允許的,甚至說是偉大的,光明的,正確的思想,是有
道德的,脫離了低級趣味的,有益于人民的行為!!!----孔子語錄補集第123頁第4行,1989年10月版)
如果一個對象在構(gòu)造時要求有參數(shù),而它又有一個沒有參數(shù)的重載的構(gòu)造方法,你想想它肯定在沒有
參數(shù)的構(gòu)造方法中調(diào)用了默認參數(shù),你要做的就是把它們打印出來.有多少人是這樣做的?
jdbc optional API的其它擴展功能:
javax.sql不僅僅是在性能上的提高,而且它還支持分布式事務(wù),在傳統(tǒng)的連結(jié)過程中,我們
可以在一個連結(jié)過程中,setAutoCommit()為fasle,然后通過rollback()或commit()那回滾和提交事
務(wù),這種在一個連結(jié)過程中的事務(wù)稱為本地事務(wù),但假如在一個事務(wù)中要對多個數(shù)據(jù)庫操作,或多過
Servlet參與操作,那就必須使用分布式事務(wù).
關(guān)于JDBC的事務(wù)我會放在下面來介紹,一個值得慶賀的功能出來了,就是事務(wù)保存點已經(jīng)
JDBC3.0中實現(xiàn),以前,如果我們把事務(wù)原子A,B,C放在一個事務(wù)中,如果A,B執(zhí)行了,C失敗,我們只能
把A,B都回滾了,但現(xiàn)在我們可以先把A,B保存為一個點,然后以這個點為回滾或提交,這就象在用WORD
編寫文章時我們可以在不同的時候保存一個副本,而不會要么一字沒有了,要么就是當(dāng)前編輯的狀態(tài).
現(xiàn)在我們來優(yōu)化我們在基礎(chǔ)知識中實現(xiàn)的Bean,今天在家,沒法上論壇,上次寫的連結(jié)部分
叫什么名字忘記了,現(xiàn)在我們就叫它PooledDB吧.
當(dāng)時我們已經(jīng)把那個Bean分為三個部分,把生成連結(jié)部分獨立出來了,而業(yè)務(wù)方法部份和擴
展部分根本不要動它,這就是繼承的好處:)
package com.inmsg.beans;
import javax.naming.*;
import javax.sql.*;
public class PooledDB {
Connection con = null;
private String source = "";
public PooledDB() throws Exception {//默認構(gòu)造方法,如果構(gòu)造時不加參數(shù),連結(jié)jdbc/office
source = "java:comp/env/jdbc/office";
Context ct = new InitialContext();
DataSource ds = (DataSource) ct.lookup(source);
con = ds.getConnection();
}
//然后增加重載方法,用來連結(jié)其它的數(shù)據(jù)源
public PooledDB(String source) throws Exception {
this.source = source;
Context ct = new InitialContext();
DataSource ds = (DataSource) ct.lookup(source);
con = ds.getConnection();
}
//注意一定要先把source賦給成員變量this.source,因為下面還有一個makeConnection()
//輔助方法,如果不把source賦給this.source,則makeConnection()調(diào)用默認的source字符串
private void makeConnection() throws Exception {
Context ct = new InitialContext();
DataSource ds = (DataSource) ct.lookup(source);
con = ds.getConnection();
}
//現(xiàn)在我們把close()方法拿到父類來實現(xiàn),這是經(jīng)過綜合考慮的,它是一個業(yè)務(wù)方法,無論是什么
//方式取得連結(jié),它本身不會修改,但為什么還封裝到父類中呢?因為這樣可以用一個獨立的父類來
//做連結(jié)測試,如果我只想試一下數(shù)據(jù)庫能不能連結(jié),我就不必再引用子類,直接用這個父類就行了
public void close() throws Exception{
if(con != null && !con.isClosed()) con.close();
}
}
一般來說,構(gòu)造方法盡量捕獲異常處理而不要拋出異常,但作為Bean的實現(xiàn),捕獲異常調(diào)用者不容易
看到異常信息,所以拋給調(diào)用者處理,另外這個類又要在應(yīng)用程序中調(diào)用,又要考慮作為Bean調(diào)用,所以一定
要有一個無參數(shù)的構(gòu)造方法,否則不能作為javaBean調(diào)用.把異常拋出給調(diào)用者的另一目的,我在設(shè)計時是這
樣考慮的,就是強迫你一定在使用try{}catch(){}塊,這樣你就會想到再加一個finally{},再次提醒,一定要
以下面的形式來調(diào)用你數(shù)據(jù)庫連結(jié)的Bean或封裝類:
PooledDB pd = null;
try{
pd = new PooledDB();
....................
}
catch(Exception e){}
finally{try{pd.close();}catch(Exception ex){}}
如果要測試你的數(shù)據(jù)庫連結(jié)是否有泄漏,請你把DataSource中最大連結(jié)數(shù)設(shè)為1,只用一個連結(jié)的情
況下,如果你的程序中哪一處沒有關(guān)閉連結(jié),則下面的程序就不能再訪問,然后從頭到尾測試你的程序吧,一旦
發(fā)現(xiàn)不能訪問數(shù)據(jù)庫了,就查看剛才訪問的代碼,這樣所有程序測試后,就可以放心了,一般來說我是不用這么
測試的,因為我在寫數(shù)據(jù)庫連結(jié)時是沒有生成對象就寫好close:
PooledDB pd = null;
try{}
catch(Exception e){}
finally{try{pd.close();}catch(Exception ex){}}
然后原在try{}的花括號中回車加上pd = PooledDB();和業(yè)務(wù)代碼的 :)當(dāng)然,你們都比我聰明用不
著這樣死套也不會忘記close()的.
不要太高興,到目前為止你仍然還沒得到一個最好的解決方案,以下我們還會對這個數(shù)據(jù)庫連結(jié)的
Bean(類)不斷優(yōu)化的..................................
好了,javax.sql的連結(jié)先說到這兒吧,今天是周六,出去玩一會了(廣州街頭上沒有什么美女!)