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