javaGrowing

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            92 隨筆 :: 33 文章 :: 49 評論 :: 0 Trackbacks
          JDBC基礎(chǔ)(一)

          本來不想寫這部份入門級的內(nèi)容,但既然欄目定為JDBC專欄,還是簡單寫一些吧.
          JDBC基礎(chǔ)(一)

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

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

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


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

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

              一個普通數(shù)據(jù)庫的連結(jié)過程為:
              1.加載驅(qū)動程序.
              2.通過DriverManager到得一個與數(shù)據(jù)庫連結(jié)的句柄.
              3.通過連結(jié)句柄綁定要執(zhí)行的語句.
              4.接收執(zhí)行結(jié)果.
              5.可選的對結(jié)果的處理.
              6.必要的關(guān)閉和數(shù)據(jù)庫的連結(jié).
          JDBC基礎(chǔ)(二)

          因為是基礎(chǔ)篇,所以還是對每一步驟簡單說明一下吧:

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

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

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

              按上面介紹的步驟,一個完成的例子是這樣的:(注意,為了按上面的步驟介紹,這個例
          子不是最好的)
              try{
                  Class.forName("org.gjt.mm.mysql.Driver");
              }catch(Exception e){
                  System.out.println("沒有成功加載驅(qū)動程序:"+e.toString());
                  return;
              }//對于象我這樣的經(jīng)驗,可以直接從e.toString()的簡單的幾個字判斷出異常原因,
               //如果你是一個新手應(yīng)該選捕獲它的子類,如何知道要捕獲哪幾個異常呢?一個簡單
               //的方法就是先不加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("數(shù)據(jù)庫操作出現(xiàn)異常:"+e.toString());
              }
              finally{
                  try{conn.close();}catch(Exception){}
              }//不管你以前是學習到的關(guān)于數(shù)據(jù)庫流程是如何操作的,如果你相信我,從現(xiàn)在開始,
               //請你一定要把數(shù)據(jù)庫關(guān)閉的代碼寫到finally塊中,切切!
          JDBC基礎(chǔ)(三)

          關(guān)于Statement對象:
              前面說過,Statement對象是用來綁定要執(zhí)行的操作的,在它上面有三種執(zhí)行方法:
          即用來執(zhí)行查詢操作的executeQuery(),用來執(zhí)行更新操作的executeUpdate()和用來執(zhí)行
          動態(tài)的未知的操作的execute().
              JDBC在編譯時并不對要執(zhí)行的SQL語句檢測,只是把它看著一個String,只有在驅(qū)動
          程序執(zhí)行SQL語句時才知道正確與否.
              一個Statement對象同時只能有一個結(jié)果集在活動.這是寬容性的,就是說即使沒有
          調(diào)用ResultSet的close()方法,只要打開第二個結(jié)果集就隱含著對上一個結(jié)果集的關(guān)閉.所以
          如果你想同時對多個結(jié)果集操作,就要創(chuàng)建多個Statement對象,如果不需要同時操作,那么可
          以在一個Statement對象上須序操作多個結(jié)果集.
              
              這里我不得不特別說明一下,很多人會用一個Statement進行嵌套查詢,然后就來問
          我說為什么不能循環(huán)?道理上面已經(jīng)說清楚了.我們來詳細分析一下嵌套查詢:
              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時,這時隱含的操作
          是已經(jīng)關(guān)閉了rs,你還能循環(huán)下去嗎?
          所以如果要同時操作多個結(jié)果集一定要讓它他綁定到不同的Statement對象上.好在一個connection
          對象可以創(chuàng)建任意多個Statement對象,而不需要你重新獲取連結(jié).

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

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

              作為基礎(chǔ)知識的最后部分,我們來說一說結(jié)果集的處理,當然是說對一般結(jié)果集的處理.
          至于存儲過程返回的多結(jié)果集,我們?nèi)匀环旁诟呒墤?yīng)用中介紹.
              SQL語句如何執(zhí)行的是查詢操作,那就要返回一個ResultSet對象,要想把查詢結(jié)果最后
          明白地顯示給用戶,必須對ResultSet進行處理.ResultSet返回的是一個表中符合條件的記錄,對
          ResultSet的處理要逐行處理,而對于每一行的列的處理,則可以按任意順序(注意,這只是JDBC規(guī)
          范的要求,有些JDBC實現(xiàn)時對于列的處理仍然要求用戶按順序處理,但這是極少數(shù)的).事實上,雖
          然你可以在處理列的時候可以按任意順序,但如果你按從左到右的順序則可以得到較高的性能.

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

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

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

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

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

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

              獲取結(jié)果集的信息:
              大多數(shù)情況下編程人員對數(shù)據(jù)庫結(jié)構(gòu)是了解的,可以知道結(jié)果集中各列的情況,但有時并
          不知道結(jié)果集中有哪些列,是什么類型.這時可以通過getMetaData()來獲取結(jié)果集的情況:

              ResulSetMetaData rsmd = rs.getMetaData();
              rsmd.getColumnCount()返回列的個數(shù).
              getColumnLabel(int)返回該int所對應(yīng)的列的顯示標題
              getColumnName(int)返回該int所對應(yīng)的列的在數(shù)據(jù)庫中的名稱.
              getColumnType(int)返回該int所對應(yīng)的列的在數(shù)據(jù)庫中的數(shù)據(jù)類型.
              getColumnTypeName(int)返回該int所對應(yīng)的列的數(shù)據(jù)類型在數(shù)據(jù)源中的名稱.
              isReadOnly(int)返回該int所對應(yīng)的列是否只讀.
              isNullable(int)返回該int所對應(yīng)的列是否可以為空
          posted on 2006-01-09 10:04 javaGrowing 閱讀(346) 評論(0)  編輯  收藏 所屬分類: JDBC

          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導航:
           
          主站蜘蛛池模板: 大理市| 东丰县| 山东省| 博乐市| 大同县| 湘阴县| 合水县| 敦化市| 漳州市| 陕西省| 东兰县| 遂溪县| 渑池县| 阳山县| 德阳市| 加查县| 胶南市| 体育| 巫山县| 凤台县| 台北市| 民县| 湾仔区| 霞浦县| 石渠县| 保康县| 灵台县| 双辽市| 襄城县| 天等县| 双江| 高唐县| 长阳| 永和县| 蓬安县| 兴义市| 定安县| 建始县| 福州市| 泰州市| 鄂尔多斯市|