JDBC的高級應用一 ——關於數據庫連結

轉自:cnjbb論壇的jdbc專欄的axman大俠寫的-JDBC的高級應用一
鏈接:http://www.cnjbb.org/thread.jsp?boardid=23&threadid=24492


    我們所說有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的連結先說到這兒吧
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章