JDBC基礎

JDBC基礎(一)

    來,我們認識一下!
    JDBC,JAVA平臺的DATABASE的連通性.白話一句,什麼意思啊?
    就是JAVA平臺上和數據庫進行連結的"工具".

    還是先一起來回顧一下接口吧:從下向上,接口是對"案例"的抽象,由一個案例抽象出一些規則.
反過來,從上向下,被抽象出來的接口是對案例的一種承諾和約束.
    也就是說,只要你實現我規定的接口,你的類就已經具有了接口對外承諾的方法,只要"客戶"會
操作接口,不需要重新學習就會操作實現了該接口的新類!
    好了,用行話來說:
    1.通過接口可以實現不相關的類的相同行爲.
    2.通過接口可以指明多個類需要實現的方法.
    3.通過接口可以瞭解對象的交互方法而不需要了解對象所對應的類藍本.
    這幾句話很明白吧?好象有一本什麼模式的書把這段話用了30多頁寫出來,結果別人看了還不如
我這幾句話明白,不過我明白了爲什麼有些人要寫書了.

    搞懂了以上這東西,JDBC就好明白了.
    爲了通用,JAVA中要求有一種機制,在操作不同廠商數據庫時有相同的方法去操作,而不是每接
觸一種數據庫就要學習新的方法.完成這種機制的"東西"就叫"JDBC"了.
    簡單地分,JDBC有兩部分組成,JDBC API和JDBC Driver Interface.
    JDBC API就是提供給"客戶"(就是象你我這種菜鳥級程序員來用的,如果是高手都自己寫JDBC了,
哈哈)的一組獨立於數據庫的API,對任何數據庫的操作,都可以用這組API來進行.那麼要把這些通用的API
翻譯成特定數據庫能懂的"指令",就要由JDBC Driver Interface來實現了,所以這部份是面向JDBC驅動程
序開發商的編程接口,它會把我們通過JDBC API發給數據庫的通用指令翻譯給他們自己的數據庫.


    還是通過實際操作來看看JDBC如何工作的吧.

    因爲JDBC API是通用接口,那麼程序是如何知道我要連結的是哪種數據庫呢?所以在和數據庫連
結時先要加載(或註冊可用的Driver),其實就是JDBC簽名.加載驅動程序和好多方法,最常用的就是先把驅
動程序類溶解到內存中,作爲"當前"驅動程序.注意"當前"是說內存中可以有多個驅動程序,但只有現在加
載的這個作爲首選連結的驅動程序.
    Class.forName("org.gjt.mm.mysql.Driver");
    Class.forName方法是先在內存中溶解簽名爲"org.gjt.mm.mysql.Driver"的Driver類,Driver類
就會把相應的實現類對應到JDBC API的接口中.比如把org.gjt.mm.mysql.Connection的實例對象賦給
java.sql.Connection接口句柄,以便"客戶"能通過操作java.sql.Connection句柄來調用實際的
org.gjt.mm.mysql.Connection中的方法.之於它們是如果映射的,這是廠商編程的,"客戶"只要調用
Class.forName("org.gjt.mm.mysql.Driver");方法就可以順利地操作JDBC API了.

    一個普通數據庫的連結過程爲:
    1.加載驅動程序.
    2.通過DriverManager到得一個與數據庫連結的句柄.
    3.通過連結句柄綁定要執行的語句.
    4.接收執行結果.
    5.可選的對結果的處理.
    6.必要的關閉和數據庫的連結.


JDBC基礎(二)

因爲是基礎篇,所以還是對每一步驟簡單說明一下吧:

    前面說是,註冊驅動程序有多方法,Class.forName();是一種顯式地加載.當一個驅
動程序類被Classloader裝載後,在溶解的過程中,DriverManager會註冊這個驅動類的實例.
這個調用是自動發生的,也就是說DriverManager.registerDriver()方法被自動調用了,當然
我們也可以直接調用DriverManager.registerDriver()來註冊驅動程序,但是,以我的經驗.
MS的瀏覽中APPLET在調用這個方法時不能成功,也就是說MS在瀏覽器中內置的JVM對該方法的
實現是無效的.
    另外我們還可以利用系統屬性jdbc.drivers來加載多個驅動程序:
System.setProperty("jdbc.drivers","driver1:driver2:.....:drivern");多個驅動程序之
間用":"隔開,這樣在連結時JDBC會按順序搜索,直到找到第一個能成功連結指定的URL的驅動
程序.
    在基礎篇裏我們先不介紹DataSource這些高級特性.

    在成功註冊驅動程序後,我們就可以用DriverManager的靜態方法getConnection來得
到和數據庫連結的引用:
    Connection conn = DriverManager.getConnection(url);
    如果連結是成功的,則返回Connection對象conn,如果爲null或拋出異常,則說明沒有
和數據庫建立連結.
    對於getConnection()方法有三個重載的方法,一種是最簡單的只給出數據源即:
getConnection(url),另一種是同時給出一些數據源信息即getConnection(url,Properties),
另外一種就是給出數據源,用戶名和密碼:getConnection(url,user,passwod),對於數據源信息.
如果我們想在連結時給出更多的信息可以把這些信息壓入到一個Properties,當然可以直接壓
入用戶名密碼,別外還可以壓入指定字符集,編碼方式或默認操作等一些其它信息.
    
    在得到一個連結後,也就是有了和數據庫找交道的通道.我們就可以做我們想要的操
作了.
    還是先來介紹一些一般性的操作:
    如果我們要對數據庫中的表進行操作,要先緣故綁定一個語句:
    Statement stmt = conn.createStatement();
    然後利用這個語句來執行操作.根本操作目的,可以有兩種結果返回,如果執行的查詢
操作,返回爲結果集ResultSet,如果執行更新操作,則返回操作的記錄數int.
    注意,SQL操作嚴格區分只有兩個,一種就是讀操作(查詢操作),另一種就是寫操作(更
新操作),所以,create,insert,update,drop,delete等對數據有改寫行爲的操作都是更新操作.

    ResultSet rs = stmt.executeQuery("select * from table where xxxxx");
    int x = stmt.executeUpdate("delete from table where ......");
    如果你硬要用executeQuery執行一個更新操作是可以的,但不要把它賦給一個句柄,
當然稍微有些經驗的程序員是不會這麼做的.
    至於對結果集的處理,我們放在下一節討論,因爲它是可操作的可選項,只有查詢操作
才返回結果集,對於一次操作過程的完成,一個非常必要的步驟是關閉數據庫連結,在你沒有了
解更多的JDBC知識這前,你先把這一步驟作爲JDBC操作中最最重要的一步,在以後的介紹中我會
不斷地提醒你去關閉數據庫連結!!!!!!!!!!!

    按上面介紹的步驟,一個完成的例子是這樣的:(注意,爲了按上面的步驟介紹,這個例
子不是最好的)
    try{
        Class.forName("org.gjt.mm.mysql.Driver");
    }catch(Exception e){
        System.out.println("沒有成功加載驅動程序:"+e.toString());
        return;
    }//對於象我這樣的經驗,可以直接從e.toString()的簡單的幾個字判斷出異常原因,
     //如果你是一個新手應該選捕獲它的子類,如何知道要捕獲哪幾個異常呢?一個簡單
     //的方法就是先不加try{},直接Class.forName("org.gjt.mm.mysql.Driver");,編
     //譯器就會告訴你要你捕獲哪幾個異常了,當然這是偷機取巧的方法,最好還是自己
     //去看JDK文檔,它會告訴你每個方法有哪些異常要你捕獲.
    Connection conn = null;
    try{
        conn = DriverManager.getConnection(
                        "jdbc:mysql://host:3306/mysql",
                        "user",
                        "passwd");
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("select * from table");
        //rs 處理
        [rs.close();]
        [stmt.close();]
    }
    catch(Exception e){
        System.out.println("數據庫操作出現異常:"+e.toString());
    }
    finally{
        try{conn.close();}catch(Exception){}
    }//不管你以前是學習到的關於數據庫流程是如何操作的,如果你相信我,從現在開始,
     //請你一定要把數據庫關閉的代碼寫到finally塊中,切切!


JDBC基礎(三)

關於Statement對象:
    前面說過,Statement對象是用來綁定要執行的操作的,在它上面有三種執行方法:
即用來執行查詢操作的executeQuery(),用來執行更新操作的executeUpdate()和用來執行
動態的未知的操作的execute().
    JDBC在編譯時並不對要執行的SQL語句檢測,只是把它看着一個String,只有在驅動
程序執行SQL語句時才知道正確與否.
    一個Statement對象同時只能有一個結果集在活動.這是寬容性的,就是說即使沒有
調用ResultSet的close()方法,只要打開第二個結果集就隱含着對上一個結果集的關閉.所以
如果你想同時對多個結果集操作,就要創建多個Statement對象,如果不需要同時操作,那麼可
以在一個Statement對象上須序操作多個結果集.
    
    這裏我不得不特別說明一下,很多人會用一個Statement進行嵌套查詢,然後就來問
我說爲什麼不能循環?道理上面已經說清楚了.我們來詳細分析一下嵌套查詢:
    Connection conn = null;
    Statement stmt = null;
    conn = .......;
    stmt = conm.createStatement(xxxxxx);
    ResultSet rs = stmt.executeQuery(sql1);
    while(rs.next()){
        str = rs.getString(xxxxx);
        ResultSet rs1 = stmt.executeQuery("select * from 表 where 字段=str");
    }
當stmt.executeQuery("select * from 表 where 字段=str");賦給rs1時,這時隱含的操作
是已經關閉了rs,你還能循環下去嗎?
所以如果要同時操作多個結果集一定要讓它他綁定到不同的Statement對象上.好在一個connection
對象可以創建任意多個Statement對象,而不需要你重新獲取連結.

    關於獲取和設置Statement的選項:只要看看它的getXXX方法和setXXX方法就明白了,這兒
作爲基礎知識只提一下以下幾個:
    setQueryTimeout,設置一個SQL執行的超時限制.
    setMaxRows,設置結果集能容納的行數.
    setEscapeProcessing,如果參數爲true,則驅動程序在把SQL語句發給數據庫前進行轉義替
換,否則讓數據庫自己處理,當然這些默認值都可以通過get方法查詢.

    Statement的兩個子類:
    PreparedStatement:對於同一條語句的多次執行,Statement每次都要把SQL語句發送給數據
庫,這樣做效率明顯不高,而如果數據庫支持預編譯,PreparedStatement可以先把要執行的語句一次發
給它,然後每次執行而不必發送相同的語句,效率當然提高,當然如果數據庫不支持預編譯,
PreparedStatement會象Statement一樣工作,只是效率不高而不需要用戶工手干預.
    另外PreparedStatement還支持接收參數.在預編譯後只要傳輸不同的參數就可以執行,大大
提高了性能.
        
    PreparedStatement ps = conn.prepareStatement("select * from 表 where 字段=?");
    ps.setString(1,參數);
    ResultSet rs = ps.executeQuery();
    
    CallableStatement:是PreparedStatement的子類,它只是用來執行存儲過程的.
    CallableStatement sc = conn.prepareCall("{call query()}");
    ResultSet rs = cs.executeQuery();
    
    關於更高級的知識我們在JDBC高級應用中介紹.


JDBC基礎(四)

    作爲基礎知識的最後部分,我們來說一說結果集的處理,當然是說對一般結果集的處理.
至於存儲過程返回的多結果集,我們仍然放在高級應用中介紹.
    SQL語句如何執行的是查詢操作,那就要返回一個ResultSet對象,要想把查詢結果最後
明白地顯示給用戶,必須對ResultSet進行處理.ResultSet返回的是一個表中符合條件的記錄,對
ResultSet的處理要逐行處理,而對於每一行的列的處理,則可以按任意順序(注意,這只是JDBC規
範的要求,有些JDBC實現時對於列的處理仍然要求用戶按順序處理,但這是極少數的).事實上,雖
然你可以在處理列的時候可以按任意順序,但如果你按從左到右的順序則可以得到較高的性能.

    這兒從底層來講解一下ResultSet對象,在任何介紹JDBC的書上你是不會獲得這樣的知
識的,因爲那是數據庫廠商的事.ResultSet對象實際維護的是一個二維指針,第一維是指向當前
行,最初它指向的是結果集的第一行之前,所以如果要訪問第一行,就要先next(),以後每一行都
要先next()才能訪問,然後第二維的指針指向列,只要當你去rs.getXXX(列)時,才通過
Connection再去數據庫把真實的數據取出來,否則沒有什麼機器能真的把要取的數據都放在內
存中.
    所以,千萬要記住,如果Connection已經關閉,那是不可能再從ResultSet中取到數據的.
有很多人問我,我可不可以取到一個ResultSet把它寫到Session中然後關閉Connection,這樣就
不要每次都連結了.我只能告訴你,你的想法非常好,但,是錯誤的!當然在javax.sql包中JDBC高
級應用中有CacheRow和WebCacheRow可以把結果集緩存下來,但那和我們自己開一個數據結構把
ResultSet的行集中所有值一次取出來保存起來沒有什麼兩樣.
    訪問行中的列,可以按字段名或索引來訪問.下面是一個簡單的檢索結果的程序:

    ResultSet rs = stmt.executeQuery("select a1,a2,a3 from table");
    while(rs.next()){
        int i = rs.getInt(1);
        String a = rs.getString("a2");
        ..............
    }

    對於用來顯示的結果集,用while來進行next()是最普通的,如果next()返回false,則
說明已經沒有可用的行了.但有時我們可能連一行都沒有,而如果有記錄又不知道是多少行,這時
如果要對有記錄和沒有記錄進行不同的處理,應該用以下流程進行判斷:

    if(rs.next()){
        //因爲已經先next()了,所經對記錄應該用do{}while();來處理
        do{
            int i = rs.getInt(1);
            String a = rs.getString("a2");
        }while(rs.next());
    }
    esle{
        System.out.println("沒有取得符合條件的記錄!");
    }

    類型轉換:
    ResultSet的getXXX方法將努力把結果集中的SQL數據類型轉換爲JAVA的數據類型,事實
大多數類型是可以轉換的,但仍然有不少糊弄是不能轉換的,如你不能將一個SQL的float轉換成
JAVA的DATE,你無法將 VARCHAR "我們"轉換成JAVA的Int.

    較大的值:
    對於大於Statement中getMaxFieldSize返回值的值,用普通的getBytes()或getString()
是不能讀取的,好在JAVA提供了讀取輸入浪的方法,對於大對象,我們可以通過rs.getXXXStream()
來得到一個InputStream,XXX的類型包括Ascii,Binay,Unicode.根據你存儲的字段類型來使用不
同的流類型,一般來說,二進制文件用getBinayStream(),文本文件用getAsciiStyream(),對於
Unicode字符的文本文件用getUnicodeStream(),相對應的數據庫字段類型應該爲:Blob,Clob和
Nlob.

    獲取結果集的信息:
    大多數情況下編程人員對數據庫結構是瞭解的,可以知道結果集中各列的情況,但有時並
不知道結果集中有哪些列,是什麼類型.這時可以通過getMetaData()來獲取結果集的情況:

    ResulSetMetaData rsmd = rs.getMetaData();
    rsmd.getColumnCount()返回列的個數.
    getColumnLabel(int)返回該int所對應的列的顯示標題
    getColumnName(int)返回該int所對應的列的在數據庫中的名稱.
    getColumnType(int)返回該int所對應的列的在數據庫中的數據類型.
    getColumnTypeName(int)返回該int所對應的列的數據類型在數據源中的名稱.
    isReadOnly(int)返回該int所對應的列是否只讀.
    isNullable(int)返回該int所對應的列是否可以爲空 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章