JDBC高級(jí)應(yīng)用 一
關(guān)于數(shù)據(jù)庫連結(jié)
我們所說有JDBC高級(jí)應(yīng)用,并不是說它的技術(shù)含量很高(也許JAVA平臺(tái)上不存在什么"技術(shù)含量"的說
法,因?yàn)镴AVA是給大家用的而不是給某些人用的).說它是高級(jí)應(yīng)用,是因?yàn)樗菍τ贘DBC基礎(chǔ)應(yīng)用來
說的擴(kuò)展,也就是可以優(yōu)化你的應(yīng)用性能,或方便于應(yīng)用的實(shí)現(xiàn).所以說它是一種高級(jí)應(yīng)用而不叫高級(jí)
技術(shù).
JDBC中,java.sql包是基礎(chǔ)的,也是核心的功能,javax.sql包則是高級(jí)的,擴(kuò)展的功能.所以
為了交流的方便,我們把它們區(qū)分為core API和optional API.
但是仍然然有一些core API中的功能,我把它歸納到高級(jí)應(yīng)用中.象存儲(chǔ)過程的調(diào)用,多結(jié)果集
的處理,我之所以要把這些東西拿出來說明,是目前你在網(wǎng)上找不到任何一份詳細(xì)的文檔和例程,可以
說有95%以上的開發(fā)人員都不知道真正如何處理這些工作.所以我會(huì)在回上海后詳細(xì)寫這一段的內(nèi)容.
現(xiàn)在我們還是來看看optional API給我們帶來的好處:
我們已經(jīng)了解,執(zhí)行一個(gè)SQL語句,要經(jīng)過如下幾步:
1.Connction
2.Statement
3.Statement.executeXXXXX();
4.可選的對結(jié)果集的處理
5.必要的Connction的關(guān)閉.(再次提醒如果你想成為中級(jí)水平以上的程序員,請你把關(guān)閉語
句寫在finally塊中,在通過下面的介紹后我介紹一個(gè)方法可以用來驗(yàn)證你的程序是否有連結(jié)
泄漏)
這其中,生成Connction對象是最最重要的工作,也是最消耗資源的,因?yàn)檫B結(jié)對象要驅(qū)動(dòng)底層
的SOCKET,調(diào)用物理連結(jié)和數(shù)據(jù)庫進(jìn)行通信,所以生成,關(guān)閉,再生成這種連結(jié)對象就相當(dāng)于我們在二十
世紀(jì)八十年代(1980年以后出身的不了解吧?)喝易拉罐飲料一樣.你買一瓶飲料是一塊二角錢,你可知道
那罐子(Connection)值一塊零八分.而你喝下去的東西只值一角二分錢,這是我們那兒一個(gè)飲料廠的真實(shí)
數(shù)據(jù).
在javax.sql包出來以前,我們只能買這樣的飲料來喝,除非你不喝.也有一些人不服氣自己生
產(chǎn)飲料(poolman),可是消費(fèi)者很快發(fā)現(xiàn),它只是把原來單賣的易拉罐現(xiàn)在打包賣給了我們,因?yàn)樗€是
用原來的包裝原料來生產(chǎn)的,poolman這種類型的連結(jié)池,其根本是從DriverManager中g(shù)etConnection
出來的.真正的效率如果不是你心理作用的話,也許比單個(gè)連結(jié)還要低!!!以及一些江湖好漢的"杰作",
都無法跳出這個(gè)框框.我自己在那一段時(shí)間也曾醉心于研究這些"連結(jié)池",因?yàn)檎l都可以把別人的原碼
讀過后,再加上其他人的優(yōu)點(diǎn)寫出一個(gè)更好的來,可是,大家可以看到,我寫出了好用的Upload,DownLoad,
HtmlUtil,Encoder,Decoder等一系列工具.可是我沒有寫出成功的連結(jié)池.......
我們再來深入一步,為什么DriverManager生成的連結(jié)和基于它的連結(jié)池不能真正提高性能.
DriverManager對象中,絕大多數(shù)的JDBC是封裝了一個(gè)物理連結(jié),也就是它抓住了一個(gè)和數(shù)據(jù)庫通信的
Socket,當(dāng)你使用DriverManager.getConnection()時(shí)也就是有一個(gè)和數(shù)據(jù)庫連結(jié)的Socket讓你占用了.
而且這個(gè)方法是同步的.大家知道這樣的物理連結(jié)對于任何系統(tǒng)是有限制的,比如一個(gè)WEB服務(wù)器一般
最大并發(fā)是150到250之間,數(shù)據(jù)庫服務(wù)器也是這樣的道理,你不僅要考慮你的程序不能用光連結(jié),還要
考慮不同Runtime中或其它應(yīng)用程序也在同時(shí)和你一起使用這些物理連結(jié),如果一臺(tái)服務(wù)器上有JAVA
WEB SERVER,還有一個(gè)C的應(yīng)用程序也訪問數(shù)據(jù)庫,你不能那么無禮地要求人家C程序員他的程序必須
等你的JAVA調(diào)用空閑才能訪問數(shù)據(jù)庫吧.所以物理連結(jié)是極其寶貴的.
基于DriverManager.getConnection()的連結(jié)池只不過是預(yù)先生成這樣的物理連結(jié)放在一個(gè)
pool中,然后編號(hào)等你調(diào)用,它省略的是生成這樣的連結(jié)的時(shí)間.注意你得到的連結(jié)在你沒有釋放之前,
它無法處理別的工作,因?yàn)檫B結(jié)句柄在用戶手中,另外這種連結(jié)池調(diào)用時(shí)是由程序調(diào)用者初始化的,
每一次調(diào)用都必須有初始化工作,而調(diào)用者是否以優(yōu)化的方法去運(yùn)行它,完成還要看每個(gè)人的編程水
平.另一方面,如果這種連結(jié)池是如果用于WEB容器管理,那簡單是垃圾,因?yàn)樗鼜?qiáng)迫使用靜態(tài)變量來
保持連結(jié),容器根本無法做到訪問控制.而且它不能在不同Runtime中被調(diào)用.
而javax.sql的實(shí)現(xiàn)采用了在用戶連結(jié)和物理連結(jié)中間加一個(gè)緩沖的中間層,它雖然也只生
成30個(gè)物理連結(jié),但用戶本身不能訪問它,DataSource返回給用戶的是一個(gè)JAVA抽象對象,客戶程序把
連結(jié)請求放回緩沖中由DataSource統(tǒng)一調(diào)度物理連結(jié)來處理,這樣可以最大程序利用寶貴的物理連結(jié)
也許現(xiàn)在的30個(gè)物理連結(jié)仍然不夠負(fù)載,你仍然需要修改實(shí)際連結(jié)數(shù),但我們知道,我們現(xiàn)在的這種連
結(jié)方式已經(jīng)不是DriverManager.getConnection()能比的了.也就是說,在同樣多的物理連結(jié)下,
DataSource可以給我們更多(是多得多的)的調(diào)用機(jī)會(huì),其實(shí),正常情況下一個(gè)從DriverManager中
getConnection()出來物理連結(jié)的負(fù)載量只有百分之幾,就是因?yàn)槟愕恼{(diào)用抓住了它的句柄而不能讓
它很好地工作.另外程序員只能返回它而不能關(guān)閉它,因?yàn)閭鹘y(tǒng)連結(jié)池中連結(jié)對象一旦由用戶關(guān)閉,
就要再次重新生成物理新的連結(jié),所以用戶只能釋放,對于非連結(jié)池和連結(jié)池得到的連結(jié)對象,
要用不同的代碼編程,簡單是一種痛苦.一旦沒有注意,在處理完數(shù)據(jù)后不是釋放而是關(guān)閉,這個(gè)錯(cuò)誤
到底是誰的過錯(cuò)?????????????
我上面說java.sql實(shí)現(xiàn)絕大多數(shù)是得到物理連結(jié),也有例外,但不是那些以前的連結(jié)池,而是
OSE,就是oracle的servlet環(huán)境,它是把servlet服務(wù)器實(shí)現(xiàn)在數(shù)據(jù)庫的地址空間上,servlet服務(wù)去調(diào)
用數(shù)據(jù)庫根本沒有通過傳統(tǒng)的連結(jié),因?yàn)閿?shù)據(jù)是"敞開"的,這就象通過網(wǎng)絡(luò)訪問其它計(jì)算機(jī)文件和訪問
本地文件的區(qū)別. 雖然它也提供標(biāo)準(zhǔn)的JDBC接口讓你調(diào)用,但它底層根本不是用JDBC封裝的.
DataSource的另外一個(gè)優(yōu)點(diǎn)就是它完全實(shí)現(xiàn)數(shù)據(jù)庫和應(yīng)用程序的分離,如何配置服務(wù)器生成
DataSource的引用和程序開發(fā)無關(guān),你在程序開發(fā)中,只要通過JDNI查找DataSource的邏輯名稱就行.
而DriverManager.getConnection()中,你不得不把數(shù)據(jù)庫驅(qū)動(dòng)程序名,訪問地址,用戶,密碼寫在你的
應(yīng)用程序中,即使可以從配置文件中讀取這些屬性串,而不同的服務(wù)器配置文件的路徑你都要修改.而
DataSource提供了標(biāo)準(zhǔn)的配置.
說來說去,如何用DataSource來連結(jié)數(shù)據(jù)庫?
非常簡單:
DataSource ds = (DataSource)new InitialContext().lookup("jdbc/mydb");
Connection conn = ds.getConnection();
當(dāng)然我是為了說明它簡單故意把它的異常給忽略了.實(shí)際應(yīng)用中.你應(yīng)該捕獲它的異常.
如果你還不明白什么是JDNI,我勸你先找一些這方面的資料看看,這可以是網(wǎng)編程方面的
基礎(chǔ)協(xié)議啊.
關(guān)于DataSource ds = (DataSource)new InitialContext().lookup("jdbc/mydb");有幾點(diǎn)
需要說明的是,你只要配置好你的服務(wù)器(tomcat,resin,weblogic)這些服務(wù)器中都有一個(gè)例子,如果
你不是很懂,你先把那個(gè)例子改動(dòng)幾個(gè)字就行了.用一段時(shí)間你就會(huì)慢慢理解它們代表什么了.
然后你在容器環(huán)境下調(diào)用new InitialContext().lookup("jdbc/mydb")容器就會(huì)自動(dòng)找到那個(gè)
DataSource對象給你調(diào)用,這個(gè)過程對用戶來說是透明的.
那邊那個(gè)聰明的朋友已經(jīng)問了,因?yàn)镈ataSource的屬性是已經(jīng)配置好的放在容器中的,那我
不在容器環(huán)境下,比如一個(gè)獨(dú)立的application,我如何能取到DataSource呢?
其實(shí),如果你能知道new InitialContext()時(shí),容器調(diào)用了哪些默認(rèn)的配置,你就可以把
這些配置參數(shù)手工加進(jìn)去而不依賴容器環(huán)境了.好在InitialContext可以getEnvironment() ,在生
成這個(gè)對象后你可以get一下看看,把這些參數(shù)記下來,以后在沒有這些參數(shù)的環(huán)境下put進(jìn)去.
這里多幾句話,談一下學(xué)習(xí)方法,我在國內(nèi)主持幾個(gè)論壇(不多,兩三個(gè)),從沒有問過別人什
么問題,java技術(shù)又不是我發(fā)明的,不可能我什么都懂,一是問了好象有損于"高手"風(fēng)范(哈哈,其實(shí)
真正的高手還是要問別人的,只有我這種假高手才不會(huì)問別人).另一方面是我根本不必問別人,比如
象application下,連結(jié)不同廠家的DataSource要put什么東西進(jìn)去呢?一是去他們的網(wǎng)站看資料,雖然
我的英語水平只有大家的10%,但我上英文網(wǎng)站的次數(shù)可能比你們多.二是看有沒有什么共用的API能
得到,好在可以getEnvironment(),但假如沒有這個(gè)方法呢?這就要看你的學(xué)習(xí)態(tài)度了,有人會(huì)這論壇
叫"高手球命",還有什么"急用,在線等待"什么的.而我,會(huì)把new InitialContext()反編譯出來看看
它調(diào)用了什么(孔子說,為了學(xué)習(xí)的目的,反編譯是允許的,甚至說是偉大的,光明的,正確的思想,是有
道德的,脫離了低級(jí)趣味的,有益于人民的行為!!!----孔子語錄補(bǔ)集第123頁第4行,1989年10月版)
如果一個(gè)對象在構(gòu)造時(shí)要求有參數(shù),而它又有一個(gè)沒有參數(shù)的重載的構(gòu)造方法,你想想它肯定在沒有
參數(shù)的構(gòu)造方法中調(diào)用了默認(rèn)參數(shù),你要做的就是把它們打印出來.有多少人是這樣做的?
jdbc optional API的其它擴(kuò)展功能:
javax.sql不僅僅是在性能上的提高,而且它還支持分布式事務(wù),在傳統(tǒng)的連結(jié)過程中,我們
可以在一個(gè)連結(jié)過程中,setAutoCommit()為fasle,然后通過rollback()或commit()那回滾和提交事
務(wù),這種在一個(gè)連結(jié)過程中的事務(wù)稱為本地事務(wù),但假如在一個(gè)事務(wù)中要對多個(gè)數(shù)據(jù)庫操作,或多過
Servlet參與操作,那就必須使用分布式事務(wù).
關(guān)于JDBC的事務(wù)我會(huì)放在下面來介紹,一個(gè)值得慶賀的功能出來了,就是事務(wù)保存點(diǎn)已經(jīng)
JDBC3.0中實(shí)現(xiàn),以前,如果我們把事務(wù)原子A,B,C放在一個(gè)事務(wù)中,如果A,B執(zhí)行了,C失敗,我們只能
把A,B都回滾了,但現(xiàn)在我們可以先把A,B保存為一個(gè)點(diǎn),然后以這個(gè)點(diǎn)為回滾或提交,這就象在用WORD
編寫文章時(shí)我們可以在不同的時(shí)候保存一個(gè)副本,而不會(huì)要么一字沒有了,要么就是當(dāng)前編輯的狀態(tài).
現(xiàn)在我們來優(yōu)化我們在基礎(chǔ)知識(shí)中實(shí)現(xiàn)的Bean,今天在家,沒法上論壇,上次寫的連結(jié)部分
叫什么名字忘記了,現(xiàn)在我們就叫它PooledDB吧.
當(dāng)時(shí)我們已經(jīng)把那個(gè)Bean分為三個(gè)部分,把生成連結(jié)部分獨(dú)立出來了,而業(yè)務(wù)方法部份和擴(kuò)
展部分根本不要?jiǎng)铀?這就是繼承的好處:)
package com.inmsg.beans;
import javax.naming.*;
import javax.sql.*;
public class PooledDB {
Connection con = null;
private String source = "";
public PooledDB() throws Exception {//默認(rèn)構(gòu)造方法,如果構(gòu)造時(shí)不加參數(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,因?yàn)橄旅孢€有一個(gè)makeConnection()
//輔助方法,如果不把source賦給this.source,則makeConnection()調(diào)用默認(rèn)的source字符串
private void makeConnection() throws Exception {
Context ct = new InitialContext();
DataSource ds = (DataSource) ct.lookup(source);
con = ds.getConnection();
}
//現(xiàn)在我們把close()方法拿到父類來實(shí)現(xiàn),這是經(jīng)過綜合考慮的,它是一個(gè)業(yè)務(wù)方法,無論是什么
//方式取得連結(jié),它本身不會(huì)修改,但為什么還封裝到父類中呢?因?yàn)檫@樣可以用一個(gè)獨(dú)立的父類來
//做連結(jié)測試,如果我只想試一下數(shù)據(jù)庫能不能連結(jié),我就不必再引用子類,直接用這個(gè)父類就行了
public void close() throws Exception{
if(con != null && !con.isClosed()) con.close();
}
}
一般來說,構(gòu)造方法盡量捕獲異常處理而不要拋出異常,但作為Bean的實(shí)現(xiàn),捕獲異常調(diào)用者不容易
看到異常信息,所以拋給調(diào)用者處理,另外這個(gè)類又要在應(yīng)用程序中調(diào)用,又要考慮作為Bean調(diào)用,所以一定
要有一個(gè)無參數(shù)的構(gòu)造方法,否則不能作為javaBean調(diào)用.把異常拋出給調(diào)用者的另一目的,我在設(shè)計(jì)時(shí)是這
樣考慮的,就是強(qiáng)迫你一定在使用try{}catch(){}塊,這樣你就會(huì)想到再加一個(gè)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,只用一個(gè)連結(jié)的情
況下,如果你的程序中哪一處沒有關(guān)閉連結(jié),則下面的程序就不能再訪問,然后從頭到尾測試你的程序吧,一旦
發(fā)現(xiàn)不能訪問數(shù)據(jù)庫了,就查看剛才訪問的代碼,這樣所有程序測試后,就可以放心了,一般來說我是不用這么
測試的,因?yàn)槲以趯憯?shù)據(jù)庫連結(jié)時(shí)是沒有生成對象就寫好close:
PooledDB pd = null;
try{}
catch(Exception e){}
finally{try{pd.close();}catch(Exception ex){}}
然后原在try{}的花括號(hào)中回車加上pd = PooledDB();和業(yè)務(wù)代碼的 :)當(dāng)然,你們都比我聰明用不
著這樣死套也不會(huì)忘記close()的.
不要太高興,到目前為止你仍然還沒得到一個(gè)最好的解決方案,以下我們還會(huì)對這個(gè)數(shù)據(jù)庫連結(jié)的
Bean(類)不斷優(yōu)化的..................................
好了,javax.sql的連結(jié)先說到這兒吧,今天是周六,出去玩一會(huì)了(廣州街頭上沒有什么美女!)
關(guān)于數(shù)據(jù)庫連結(jié)
我們所說有JDBC高級(jí)應(yīng)用,并不是說它的技術(shù)含量很高(也許JAVA平臺(tái)上不存在什么"技術(shù)含量"的說
法,因?yàn)镴AVA是給大家用的而不是給某些人用的).說它是高級(jí)應(yīng)用,是因?yàn)樗菍τ贘DBC基礎(chǔ)應(yīng)用來
說的擴(kuò)展,也就是可以優(yōu)化你的應(yīng)用性能,或方便于應(yīng)用的實(shí)現(xiàn).所以說它是一種高級(jí)應(yīng)用而不叫高級(jí)
技術(shù).
JDBC中,java.sql包是基礎(chǔ)的,也是核心的功能,javax.sql包則是高級(jí)的,擴(kuò)展的功能.所以
為了交流的方便,我們把它們區(qū)分為core API和optional API.
但是仍然然有一些core API中的功能,我把它歸納到高級(jí)應(yīng)用中.象存儲(chǔ)過程的調(diào)用,多結(jié)果集
的處理,我之所以要把這些東西拿出來說明,是目前你在網(wǎng)上找不到任何一份詳細(xì)的文檔和例程,可以
說有95%以上的開發(fā)人員都不知道真正如何處理這些工作.所以我會(huì)在回上海后詳細(xì)寫這一段的內(nèi)容.
現(xiàn)在我們還是來看看optional API給我們帶來的好處:
我們已經(jīng)了解,執(zhí)行一個(gè)SQL語句,要經(jīng)過如下幾步:
1.Connction
2.Statement
3.Statement.executeXXXXX();
4.可選的對結(jié)果集的處理
5.必要的Connction的關(guān)閉.(再次提醒如果你想成為中級(jí)水平以上的程序員,請你把關(guān)閉語
句寫在finally塊中,在通過下面的介紹后我介紹一個(gè)方法可以用來驗(yàn)證你的程序是否有連結(jié)
泄漏)
這其中,生成Connction對象是最最重要的工作,也是最消耗資源的,因?yàn)檫B結(jié)對象要驅(qū)動(dòng)底層
的SOCKET,調(diào)用物理連結(jié)和數(shù)據(jù)庫進(jìn)行通信,所以生成,關(guān)閉,再生成這種連結(jié)對象就相當(dāng)于我們在二十
世紀(jì)八十年代(1980年以后出身的不了解吧?)喝易拉罐飲料一樣.你買一瓶飲料是一塊二角錢,你可知道
那罐子(Connection)值一塊零八分.而你喝下去的東西只值一角二分錢,這是我們那兒一個(gè)飲料廠的真實(shí)
數(shù)據(jù).
在javax.sql包出來以前,我們只能買這樣的飲料來喝,除非你不喝.也有一些人不服氣自己生
產(chǎn)飲料(poolman),可是消費(fèi)者很快發(fā)現(xiàn),它只是把原來單賣的易拉罐現(xiàn)在打包賣給了我們,因?yàn)樗€是
用原來的包裝原料來生產(chǎn)的,poolman這種類型的連結(jié)池,其根本是從DriverManager中g(shù)etConnection
出來的.真正的效率如果不是你心理作用的話,也許比單個(gè)連結(jié)還要低!!!以及一些江湖好漢的"杰作",
都無法跳出這個(gè)框框.我自己在那一段時(shí)間也曾醉心于研究這些"連結(jié)池",因?yàn)檎l都可以把別人的原碼
讀過后,再加上其他人的優(yōu)點(diǎn)寫出一個(gè)更好的來,可是,大家可以看到,我寫出了好用的Upload,DownLoad,
HtmlUtil,Encoder,Decoder等一系列工具.可是我沒有寫出成功的連結(jié)池.......
我們再來深入一步,為什么DriverManager生成的連結(jié)和基于它的連結(jié)池不能真正提高性能.
DriverManager對象中,絕大多數(shù)的JDBC是封裝了一個(gè)物理連結(jié),也就是它抓住了一個(gè)和數(shù)據(jù)庫通信的
Socket,當(dāng)你使用DriverManager.getConnection()時(shí)也就是有一個(gè)和數(shù)據(jù)庫連結(jié)的Socket讓你占用了.
而且這個(gè)方法是同步的.大家知道這樣的物理連結(jié)對于任何系統(tǒng)是有限制的,比如一個(gè)WEB服務(wù)器一般
最大并發(fā)是150到250之間,數(shù)據(jù)庫服務(wù)器也是這樣的道理,你不僅要考慮你的程序不能用光連結(jié),還要
考慮不同Runtime中或其它應(yīng)用程序也在同時(shí)和你一起使用這些物理連結(jié),如果一臺(tái)服務(wù)器上有JAVA
WEB SERVER,還有一個(gè)C的應(yīng)用程序也訪問數(shù)據(jù)庫,你不能那么無禮地要求人家C程序員他的程序必須
等你的JAVA調(diào)用空閑才能訪問數(shù)據(jù)庫吧.所以物理連結(jié)是極其寶貴的.
基于DriverManager.getConnection()的連結(jié)池只不過是預(yù)先生成這樣的物理連結(jié)放在一個(gè)
pool中,然后編號(hào)等你調(diào)用,它省略的是生成這樣的連結(jié)的時(shí)間.注意你得到的連結(jié)在你沒有釋放之前,
它無法處理別的工作,因?yàn)檫B結(jié)句柄在用戶手中,另外這種連結(jié)池調(diào)用時(shí)是由程序調(diào)用者初始化的,
每一次調(diào)用都必須有初始化工作,而調(diào)用者是否以優(yōu)化的方法去運(yùn)行它,完成還要看每個(gè)人的編程水
平.另一方面,如果這種連結(jié)池是如果用于WEB容器管理,那簡單是垃圾,因?yàn)樗鼜?qiáng)迫使用靜態(tài)變量來
保持連結(jié),容器根本無法做到訪問控制.而且它不能在不同Runtime中被調(diào)用.
而javax.sql的實(shí)現(xiàn)采用了在用戶連結(jié)和物理連結(jié)中間加一個(gè)緩沖的中間層,它雖然也只生
成30個(gè)物理連結(jié),但用戶本身不能訪問它,DataSource返回給用戶的是一個(gè)JAVA抽象對象,客戶程序把
連結(jié)請求放回緩沖中由DataSource統(tǒng)一調(diào)度物理連結(jié)來處理,這樣可以最大程序利用寶貴的物理連結(jié)
也許現(xiàn)在的30個(gè)物理連結(jié)仍然不夠負(fù)載,你仍然需要修改實(shí)際連結(jié)數(shù),但我們知道,我們現(xiàn)在的這種連
結(jié)方式已經(jīng)不是DriverManager.getConnection()能比的了.也就是說,在同樣多的物理連結(jié)下,
DataSource可以給我們更多(是多得多的)的調(diào)用機(jī)會(huì),其實(shí),正常情況下一個(gè)從DriverManager中
getConnection()出來物理連結(jié)的負(fù)載量只有百分之幾,就是因?yàn)槟愕恼{(diào)用抓住了它的句柄而不能讓
它很好地工作.另外程序員只能返回它而不能關(guān)閉它,因?yàn)閭鹘y(tǒng)連結(jié)池中連結(jié)對象一旦由用戶關(guān)閉,
就要再次重新生成物理新的連結(jié),所以用戶只能釋放,對于非連結(jié)池和連結(jié)池得到的連結(jié)對象,
要用不同的代碼編程,簡單是一種痛苦.一旦沒有注意,在處理完數(shù)據(jù)后不是釋放而是關(guān)閉,這個(gè)錯(cuò)誤
到底是誰的過錯(cuò)?????????????
我上面說java.sql實(shí)現(xiàn)絕大多數(shù)是得到物理連結(jié),也有例外,但不是那些以前的連結(jié)池,而是
OSE,就是oracle的servlet環(huán)境,它是把servlet服務(wù)器實(shí)現(xiàn)在數(shù)據(jù)庫的地址空間上,servlet服務(wù)去調(diào)
用數(shù)據(jù)庫根本沒有通過傳統(tǒng)的連結(jié),因?yàn)閿?shù)據(jù)是"敞開"的,這就象通過網(wǎng)絡(luò)訪問其它計(jì)算機(jī)文件和訪問
本地文件的區(qū)別. 雖然它也提供標(biāo)準(zhǔn)的JDBC接口讓你調(diào)用,但它底層根本不是用JDBC封裝的.
DataSource的另外一個(gè)優(yōu)點(diǎn)就是它完全實(shí)現(xiàn)數(shù)據(jù)庫和應(yīng)用程序的分離,如何配置服務(wù)器生成
DataSource的引用和程序開發(fā)無關(guān),你在程序開發(fā)中,只要通過JDNI查找DataSource的邏輯名稱就行.
而DriverManager.getConnection()中,你不得不把數(shù)據(jù)庫驅(qū)動(dòng)程序名,訪問地址,用戶,密碼寫在你的
應(yīng)用程序中,即使可以從配置文件中讀取這些屬性串,而不同的服務(wù)器配置文件的路徑你都要修改.而
DataSource提供了標(biāo)準(zhǔn)的配置.
說來說去,如何用DataSource來連結(jié)數(shù)據(jù)庫?
非常簡單:
DataSource ds = (DataSource)new InitialContext().lookup("jdbc/mydb");
Connection conn = ds.getConnection();
當(dāng)然我是為了說明它簡單故意把它的異常給忽略了.實(shí)際應(yīng)用中.你應(yīng)該捕獲它的異常.
如果你還不明白什么是JDNI,我勸你先找一些這方面的資料看看,這可以是網(wǎng)編程方面的
基礎(chǔ)協(xié)議啊.
關(guān)于DataSource ds = (DataSource)new InitialContext().lookup("jdbc/mydb");有幾點(diǎn)
需要說明的是,你只要配置好你的服務(wù)器(tomcat,resin,weblogic)這些服務(wù)器中都有一個(gè)例子,如果
你不是很懂,你先把那個(gè)例子改動(dòng)幾個(gè)字就行了.用一段時(shí)間你就會(huì)慢慢理解它們代表什么了.
然后你在容器環(huán)境下調(diào)用new InitialContext().lookup("jdbc/mydb")容器就會(huì)自動(dòng)找到那個(gè)
DataSource對象給你調(diào)用,這個(gè)過程對用戶來說是透明的.
那邊那個(gè)聰明的朋友已經(jīng)問了,因?yàn)镈ataSource的屬性是已經(jīng)配置好的放在容器中的,那我
不在容器環(huán)境下,比如一個(gè)獨(dú)立的application,我如何能取到DataSource呢?
其實(shí),如果你能知道new InitialContext()時(shí),容器調(diào)用了哪些默認(rèn)的配置,你就可以把
這些配置參數(shù)手工加進(jìn)去而不依賴容器環(huán)境了.好在InitialContext可以getEnvironment() ,在生
成這個(gè)對象后你可以get一下看看,把這些參數(shù)記下來,以后在沒有這些參數(shù)的環(huán)境下put進(jìn)去.
這里多幾句話,談一下學(xué)習(xí)方法,我在國內(nèi)主持幾個(gè)論壇(不多,兩三個(gè)),從沒有問過別人什
么問題,java技術(shù)又不是我發(fā)明的,不可能我什么都懂,一是問了好象有損于"高手"風(fēng)范(哈哈,其實(shí)
真正的高手還是要問別人的,只有我這種假高手才不會(huì)問別人).另一方面是我根本不必問別人,比如
象application下,連結(jié)不同廠家的DataSource要put什么東西進(jìn)去呢?一是去他們的網(wǎng)站看資料,雖然
我的英語水平只有大家的10%,但我上英文網(wǎng)站的次數(shù)可能比你們多.二是看有沒有什么共用的API能
得到,好在可以getEnvironment(),但假如沒有這個(gè)方法呢?這就要看你的學(xué)習(xí)態(tài)度了,有人會(huì)這論壇
叫"高手球命",還有什么"急用,在線等待"什么的.而我,會(huì)把new InitialContext()反編譯出來看看
它調(diào)用了什么(孔子說,為了學(xué)習(xí)的目的,反編譯是允許的,甚至說是偉大的,光明的,正確的思想,是有
道德的,脫離了低級(jí)趣味的,有益于人民的行為!!!----孔子語錄補(bǔ)集第123頁第4行,1989年10月版)
如果一個(gè)對象在構(gòu)造時(shí)要求有參數(shù),而它又有一個(gè)沒有參數(shù)的重載的構(gòu)造方法,你想想它肯定在沒有
參數(shù)的構(gòu)造方法中調(diào)用了默認(rèn)參數(shù),你要做的就是把它們打印出來.有多少人是這樣做的?
jdbc optional API的其它擴(kuò)展功能:
javax.sql不僅僅是在性能上的提高,而且它還支持分布式事務(wù),在傳統(tǒng)的連結(jié)過程中,我們
可以在一個(gè)連結(jié)過程中,setAutoCommit()為fasle,然后通過rollback()或commit()那回滾和提交事
務(wù),這種在一個(gè)連結(jié)過程中的事務(wù)稱為本地事務(wù),但假如在一個(gè)事務(wù)中要對多個(gè)數(shù)據(jù)庫操作,或多過
Servlet參與操作,那就必須使用分布式事務(wù).
關(guān)于JDBC的事務(wù)我會(huì)放在下面來介紹,一個(gè)值得慶賀的功能出來了,就是事務(wù)保存點(diǎn)已經(jīng)
JDBC3.0中實(shí)現(xiàn),以前,如果我們把事務(wù)原子A,B,C放在一個(gè)事務(wù)中,如果A,B執(zhí)行了,C失敗,我們只能
把A,B都回滾了,但現(xiàn)在我們可以先把A,B保存為一個(gè)點(diǎn),然后以這個(gè)點(diǎn)為回滾或提交,這就象在用WORD
編寫文章時(shí)我們可以在不同的時(shí)候保存一個(gè)副本,而不會(huì)要么一字沒有了,要么就是當(dāng)前編輯的狀態(tài).
現(xiàn)在我們來優(yōu)化我們在基礎(chǔ)知識(shí)中實(shí)現(xiàn)的Bean,今天在家,沒法上論壇,上次寫的連結(jié)部分
叫什么名字忘記了,現(xiàn)在我們就叫它PooledDB吧.
當(dāng)時(shí)我們已經(jīng)把那個(gè)Bean分為三個(gè)部分,把生成連結(jié)部分獨(dú)立出來了,而業(yè)務(wù)方法部份和擴(kuò)
展部分根本不要?jiǎng)铀?這就是繼承的好處:)
package com.inmsg.beans;
import javax.naming.*;
import javax.sql.*;
public class PooledDB {
Connection con = null;
private String source = "";
public PooledDB() throws Exception {//默認(rèn)構(gòu)造方法,如果構(gòu)造時(shí)不加參數(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,因?yàn)橄旅孢€有一個(gè)makeConnection()
//輔助方法,如果不把source賦給this.source,則makeConnection()調(diào)用默認(rèn)的source字符串
private void makeConnection() throws Exception {
Context ct = new InitialContext();
DataSource ds = (DataSource) ct.lookup(source);
con = ds.getConnection();
}
//現(xiàn)在我們把close()方法拿到父類來實(shí)現(xiàn),這是經(jīng)過綜合考慮的,它是一個(gè)業(yè)務(wù)方法,無論是什么
//方式取得連結(jié),它本身不會(huì)修改,但為什么還封裝到父類中呢?因?yàn)檫@樣可以用一個(gè)獨(dú)立的父類來
//做連結(jié)測試,如果我只想試一下數(shù)據(jù)庫能不能連結(jié),我就不必再引用子類,直接用這個(gè)父類就行了
public void close() throws Exception{
if(con != null && !con.isClosed()) con.close();
}
}
一般來說,構(gòu)造方法盡量捕獲異常處理而不要拋出異常,但作為Bean的實(shí)現(xiàn),捕獲異常調(diào)用者不容易
看到異常信息,所以拋給調(diào)用者處理,另外這個(gè)類又要在應(yīng)用程序中調(diào)用,又要考慮作為Bean調(diào)用,所以一定
要有一個(gè)無參數(shù)的構(gòu)造方法,否則不能作為javaBean調(diào)用.把異常拋出給調(diào)用者的另一目的,我在設(shè)計(jì)時(shí)是這
樣考慮的,就是強(qiáng)迫你一定在使用try{}catch(){}塊,這樣你就會(huì)想到再加一個(gè)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,只用一個(gè)連結(jié)的情
況下,如果你的程序中哪一處沒有關(guān)閉連結(jié),則下面的程序就不能再訪問,然后從頭到尾測試你的程序吧,一旦
發(fā)現(xiàn)不能訪問數(shù)據(jù)庫了,就查看剛才訪問的代碼,這樣所有程序測試后,就可以放心了,一般來說我是不用這么
測試的,因?yàn)槲以趯憯?shù)據(jù)庫連結(jié)時(shí)是沒有生成對象就寫好close:
PooledDB pd = null;
try{}
catch(Exception e){}
finally{try{pd.close();}catch(Exception ex){}}
然后原在try{}的花括號(hào)中回車加上pd = PooledDB();和業(yè)務(wù)代碼的 :)當(dāng)然,你們都比我聰明用不
著這樣死套也不會(huì)忘記close()的.
不要太高興,到目前為止你仍然還沒得到一個(gè)最好的解決方案,以下我們還會(huì)對這個(gè)數(shù)據(jù)庫連結(jié)的
Bean(類)不斷優(yōu)化的..................................
好了,javax.sql的連結(jié)先說到這兒吧,今天是周六,出去玩一會(huì)了(廣州街頭上沒有什么美女!)