Posted on 2007-11-21 18:22
帥子 閱讀(190)
評(píng)論(0) 編輯 收藏 所屬分類:
J2EE技術(shù)專區(qū)
全面解析JDBC
綜述:Java數(shù)據(jù)庫(kù)連接體系結(jié)構(gòu)是用于Java應(yīng)用程序連接數(shù)據(jù)庫(kù)的標(biāo)準(zhǔn)方法。JDBC對(duì)Java程序員而言是API,對(duì)實(shí)現(xiàn)與數(shù)據(jù)庫(kù)連接的服務(wù)提供商而言是接口模型。作為API,JDBC為程序開(kāi)發(fā)提供標(biāo)準(zhǔn)的接口,并為數(shù)據(jù)庫(kù)廠商及第三方中間件廠商實(shí)現(xiàn)與數(shù)據(jù)庫(kù)的連接提供了標(biāo)準(zhǔn)方法。JDBC使用已有的SQL標(biāo)準(zhǔn)并支持與其它數(shù)據(jù)庫(kù)連接標(biāo)準(zhǔn),如ODBC之間的橋接。JDBC實(shí)現(xiàn)了所有這些面向標(biāo)準(zhǔn)的目標(biāo)并且具有簡(jiǎn)單、嚴(yán)格類型定義且高性能實(shí)現(xiàn)的接口。
如何選擇合適的JDBC產(chǎn)品?
有關(guān)JDBC最新的信息,有興趣的讀者可以查閱JDBC的官方網(wǎng)站--即JavaSoft的主頁(yè),其URL為:http://Java.sun.com/products/jdbc
1. JavaSoft框架
JavaSoft提供三種JDBC產(chǎn)品組件,它們是Java開(kāi)發(fā)工具包(JDK)的組成部份:JDBC驅(qū)動(dòng)程序管理器、JDBC驅(qū)動(dòng)程序測(cè)試工具包和JDBC-ODBC橋。
JDBC驅(qū)動(dòng)程序管理器是JDBC體系結(jié)構(gòu)的支柱。它實(shí)際上很小,也很簡(jiǎn)單;其主要作用是把Java應(yīng)用程序連接到正確的JDBC驅(qū)動(dòng)程序上,然后即退出。
JDBC驅(qū)動(dòng)程序測(cè)試工具包為使JDBC驅(qū)動(dòng)程序運(yùn)行您的程序提供一定的可信度。只有通過(guò)JDBC驅(qū)動(dòng)程序測(cè)試的驅(qū)動(dòng)程序才被認(rèn)為是符合JDBC標(biāo)準(zhǔn)TM的。
JDBC-ODBC橋使ODBC驅(qū)動(dòng)程序可被用作JDBC驅(qū)動(dòng)程序。它的實(shí)現(xiàn)為JDBC的快速發(fā)展提供了一條途徑,其長(zhǎng)遠(yuǎn)目標(biāo)提供一種訪問(wèn)某些不常見(jiàn)的DBMS(如果對(duì)這些不常見(jiàn)的DBMS未實(shí)現(xiàn)JDBC)的方法。
2. JDBC驅(qū)動(dòng)程序的類型
目前比較常見(jiàn)的JDBC驅(qū)動(dòng)程序可分為以下四個(gè)種類:
(1)JDBC-ODBC橋加ODBC驅(qū)動(dòng)程序
JavaSoft橋產(chǎn)品利用ODBC驅(qū)動(dòng)程序提供JDBC訪問(wèn)。注意,必須將ODBC二進(jìn)制代碼(許多情況下還包括數(shù)據(jù)庫(kù)客戶機(jī)代碼)加載到使用該驅(qū)動(dòng)程序的每個(gè)客戶機(jī)上。因此,這種類型的驅(qū)動(dòng)程序最適合于企業(yè)網(wǎng)(這種網(wǎng)絡(luò)上客戶機(jī)的安裝不是主要問(wèn)題),或者是用Java編寫(xiě)的三層結(jié)構(gòu)的應(yīng)用程序服務(wù)器代碼。
(2)本地API
這種類型的驅(qū)動(dòng)程序把客戶機(jī)API上的JDBC調(diào)用轉(zhuǎn)換為Oracle、Sybase、Informix、DB2或其它DBMS的調(diào)用。注意,象橋驅(qū)動(dòng)程序一樣,這種類型的驅(qū)動(dòng)程序要求將某些二進(jìn)制代碼加載到每臺(tái)客戶機(jī)上。
(3)JDBC網(wǎng)絡(luò)純Java驅(qū)動(dòng)程序
這種驅(qū)動(dòng)程序?qū)DBC轉(zhuǎn)換為與DBMS無(wú)關(guān)的網(wǎng)絡(luò)協(xié)議,之后這種協(xié)議又被某個(gè)服務(wù)器轉(zhuǎn)換為一種DBMS協(xié)議。這種網(wǎng)絡(luò)服務(wù)器中間件能夠?qū)⑺募僇ava客戶機(jī)連接到多種不同的數(shù)據(jù)庫(kù)上。所用的具體協(xié)議取決于提供者。通常,這是最為靈活的JDBC驅(qū)動(dòng)程序。有可能所有這種解決方案的提供者都提供適合于Intranet用的產(chǎn)品。為了使這些產(chǎn)品也支持Internet訪問(wèn),它們必須處理Web所提出的安全性、通過(guò)防火墻的訪問(wèn)等方面的額外要求。幾家提供者正將JDBC驅(qū)動(dòng)程序加到他們現(xiàn)有的數(shù)據(jù)庫(kù)中間件產(chǎn)品中。
(4)本地協(xié)議純Java驅(qū)動(dòng)程序
這種類型的驅(qū)動(dòng)程序?qū)DBC調(diào)用直接轉(zhuǎn)換為DBMS所使用的網(wǎng)絡(luò)協(xié)議。這將允許從客戶機(jī)機(jī)器上直接調(diào)用DBMS服務(wù)器,是Intranet訪問(wèn)的一個(gè)很實(shí)用的解決方法。由于許多這樣的協(xié)議都是專用的,因此數(shù)據(jù)庫(kù)提供者自己將是主要來(lái)源,有幾家提供者已在著手做這件事了。
據(jù)專家預(yù)計(jì)第(3)、(4)類驅(qū)動(dòng)程序?qū)⒊蔀閺腏DBC訪問(wèn)數(shù)據(jù)庫(kù)的首方法。第(1)、(2)類驅(qū)動(dòng)程序在直接的純Java驅(qū)動(dòng)程序還沒(méi)有上市前會(huì)作為過(guò)渡方案來(lái)使用。對(duì)第(1)、(2)類驅(qū)動(dòng)程序可能會(huì)有一些變種,這些變種要求有連接器,但通常這些是更加不可取的解決方案。第(3)、(4)類驅(qū)動(dòng)程序提供了Java的所有優(yōu)點(diǎn),包括自動(dòng)安裝(例如,通過(guò)使用JDBC驅(qū)動(dòng)程序的appletapplet來(lái)下載該驅(qū)動(dòng)程序)。
3. JDBC驅(qū)動(dòng)程序的獲取
目前已有幾十個(gè)(1)類的驅(qū)動(dòng)程序,即可與Javasoft橋聯(lián)合使用的ODBC驅(qū)動(dòng)程序的驅(qū)動(dòng)程序。有大約十多個(gè)屬于種類(2)的驅(qū)動(dòng)程序是以DBMS的本地API為基礎(chǔ)編寫(xiě)的。只有幾個(gè)屬于種類(3)的驅(qū)動(dòng)程序,其首批提供者是SCO、OpenHorizon、Visigenic和WebLogic。此外,JavaSoft和數(shù)據(jù)庫(kù)連接的領(lǐng)先提供者Intersolv還合作研制了JDBC-ODBC橋和JDBC驅(qū)動(dòng)程序測(cè)試工具包。
如何建立JDBC連接?
Connection 對(duì)象代表與數(shù)據(jù)庫(kù)的連接。連接過(guò)程包括所執(zhí)行的 SQL 語(yǔ)句和在該連接上所返回的結(jié)果。一個(gè)應(yīng)用程序可與單個(gè)數(shù)據(jù)庫(kù)有一個(gè)或多個(gè)連接,或者可與許多數(shù)據(jù)庫(kù)有連接。
1. 打開(kāi)連接
與數(shù)據(jù)庫(kù)建立連接的標(biāo)準(zhǔn)方法是調(diào)用DriverManager.getConnection方法。該方法接受含有某個(gè)URL的字符串。DriverManager類(即所謂的JDBC管理層)將嘗試找到可與那個(gè)URL所代表的數(shù)據(jù)庫(kù)進(jìn)行連接的驅(qū)動(dòng)程序。DriverManager類存有已注冊(cè)的Driver類的清單。當(dāng)調(diào)用方法getConnection時(shí),它將檢查清單中的每個(gè)驅(qū)動(dòng)程序,直到找到可與URL中指定的數(shù)據(jù)庫(kù)進(jìn)行連接的驅(qū)動(dòng)程序?yàn)橹埂river的方法connect使用這個(gè)URL來(lái)建立實(shí)際的連接。
用戶可繞過(guò)JDBC管理層直接調(diào)用Driver方法。這在以下特殊情況下將很有用:當(dāng)兩個(gè)驅(qū)動(dòng)器可同時(shí)連接到數(shù)據(jù)庫(kù)中,而用戶需要明確地選用其中特定的驅(qū)動(dòng)器。但一般情況下,讓DriverManager類處理打開(kāi)連接這種事將更為簡(jiǎn)單。
下述代碼顯示如何打開(kāi)一個(gè)與位于URL"jdbc:odbc:wombat"的數(shù)據(jù)庫(kù)的連接。所用的用戶標(biāo)識(shí)符為"freely",口令為"ec":
String url = "jdbc:odbc:wombat";
Connection con = DriverManager.getConnection(url, "freely", "ec");
2. 一般用法的URL
由于URL常引起混淆,我們將先對(duì)一般URL作簡(jiǎn)單說(shuō)明,然后再討論JDBCURL。URL(統(tǒng)一資源定位符)提供在Internet上定位資源所需的信息。可將它想象為一個(gè)地址。URL的第一部份指定了訪問(wèn)信息所用的協(xié)議,后面總是跟著冒號(hào)。常用的協(xié)議有"ftp"(代表"文件傳輸協(xié)議")和"http"(代表"超文本傳輸協(xié)議")。如果協(xié)議是"file",表示資源是在某個(gè)本地文件系統(tǒng)上而非在Internet上(下例用于表示我們所描述的部分;它并非URL的組成部分)。
ftp://Javasoft.com/docs/JDK-1_apidocs.zip
http://Java.sun.com/products/jdk/CurrentRelease
file:/home/haroldw/docs/books/tutorial/summary.html
URL的其余部份(冒號(hào)后面的)給出了數(shù)據(jù)資源所處位置的有關(guān)信息。如果協(xié)議是file,則URL的其余部份是文件的路徑。對(duì)于ftp和http協(xié)議,URL的其余部份標(biāo)識(shí)了主機(jī)并可選地給出某個(gè)更詳盡的地址路徑。例如,以下是JavaSoft主頁(yè)的URL。該URL只標(biāo)識(shí)了主機(jī):http://Java.sun.com。從該主頁(yè)開(kāi)始瀏覽,就可以進(jìn)到許多其它的網(wǎng)頁(yè)中,其中之一就是JDBC主頁(yè)。JDBC主頁(yè)的URL更為具體,它具體表示為:
http://Java.sun.com/products/jdbc
3. JDBC URL
JDBC URL提供了一種標(biāo)識(shí)數(shù)據(jù)庫(kù)的方法,可以使相應(yīng)的驅(qū)動(dòng)程序能識(shí)別該數(shù)據(jù)庫(kù)并與之建立連接。實(shí)際上,驅(qū)動(dòng)程序編程員將決定用什么JDBC URL來(lái)標(biāo)識(shí)特定的驅(qū)動(dòng)程序。用戶不必關(guān)心如何來(lái)形成JDBC URL;他們只須使用與所用的驅(qū)動(dòng)程序一起提供的URL即可。JDBC的作用是提供某些約定,驅(qū)動(dòng)程序編程員在構(gòu)造他們的JDBC URL時(shí)應(yīng)該遵循這些約定。
由于JDBC URL要與各種不同的驅(qū)動(dòng)程序一起使用,因此這些約定應(yīng)非常靈活。首先,它們應(yīng)允許不同的驅(qū)動(dòng)程序使用不同的方案來(lái)命名數(shù)據(jù)庫(kù)。例如,odbc子協(xié)議允許(但并不是要求)URL含有屬性值。
其次,JDBC URL應(yīng)允許驅(qū)動(dòng)程序編程員將一切所需的信息編入其中。這樣就可以讓要與給定數(shù)據(jù)庫(kù)對(duì)話的applet打開(kāi)數(shù)據(jù)庫(kù)連接,而無(wú)須要求用戶去做任何系統(tǒng)管理工作。
最后,JDBC URL應(yīng)允許某種程度的間接性。也就是說(shuō),JDBC URL可指向邏輯主機(jī)或數(shù)據(jù)庫(kù)名,而這種邏輯主機(jī)或數(shù)據(jù)庫(kù)名將由網(wǎng)絡(luò)命名系統(tǒng)動(dòng)態(tài)地轉(zhuǎn)換為實(shí)際的名稱。這可以使系統(tǒng)管理員不必將特定主機(jī)聲明為JDBC名稱的一部份。網(wǎng)絡(luò)命名服務(wù)(例如DNS、NIS和DCE)有多種,而對(duì)于使用哪種命名服務(wù)并無(wú)限制。
JDBC URL的標(biāo)準(zhǔn)語(yǔ)法如下所示。它由三部分組成,各部分間用冒號(hào)分隔:
jdbc:<子協(xié)遙荊海甲用?疲?br> JDBC URL的三個(gè)部分可分解如下:
(1)jdbc協(xié)議:JDBC URL中的協(xié)議總是jdbc。
(2)<子協(xié)議>:驅(qū)動(dòng)程序名或數(shù)據(jù)庫(kù)連接機(jī)制(這種機(jī)制可由一個(gè)或多個(gè)驅(qū)動(dòng)程序支持)的名稱。子協(xié)議名的典型示例是"odbc",該名稱是為用于指定ODBC風(fēng)格的數(shù)據(jù)資源名稱的URL專門保留的。例如,為了通過(guò)JDBC-ODBC橋來(lái)訪問(wèn)某個(gè)數(shù)據(jù)庫(kù),可以用如下所示的URL:jdbc:odbc:book。本例中,子協(xié)議為"odbc",子名稱"book"是本地ODBC數(shù)據(jù)資源。如果要用網(wǎng)絡(luò)命名服務(wù)(這樣JDBC URL中的數(shù)據(jù)庫(kù)名稱不必是實(shí)際名稱),則命名服務(wù)可以作為子協(xié)議。例如,可用如下所示的URL:jdbc:dcenaming:accounts。本例中,該URL指定了本地DCE命名服務(wù)應(yīng)該將數(shù)據(jù)庫(kù)名稱"accounts"解析為更為具體的可用于連接真實(shí)數(shù)據(jù)庫(kù)的名稱。
(3)<子名稱>:種標(biāo)識(shí)數(shù)據(jù)庫(kù)的方法。子名稱可以依不同的子協(xié)議而變化。它還可以有子名稱的子名稱(含有驅(qū)動(dòng)程序編程員所選的任何內(nèi)部語(yǔ)法)。使用子名稱的目的是為定位數(shù)據(jù)庫(kù)提供足夠的信息。前例中,因?yàn)镺DBC將提供其余部份的信息,因此用"book"就已足夠。然而,位于遠(yuǎn)程服務(wù)器上的數(shù)據(jù)庫(kù)需要更多的信息。例如,如果數(shù)據(jù)庫(kù)是通過(guò)Internet來(lái)訪問(wèn)的,則在JDBC URL中應(yīng)將網(wǎng)絡(luò)地址作為子名稱的一部份包括進(jìn)去,且必須遵循如下所示的標(biāo)準(zhǔn)URL命名約定://主機(jī)名:端口/子協(xié)議。
假設(shè)"dbnet"是個(gè)用于將某個(gè)主機(jī)連接到Internet上的協(xié)議,則JDBC URL應(yīng)為:jdbc:dbnet://wombat:356/fred。
4. "odbc"子協(xié)議
子協(xié)議odbc是一種特殊情況。它是為用于指定ODBC風(fēng)格的數(shù)據(jù)資源名稱的URL而保留的,并具有下列特性:允許在子名稱(數(shù)據(jù)資源名稱)后面指定任意多個(gè)屬性值。odbc子協(xié)議的完整語(yǔ)法為:
jdbc:odbc:<數(shù)據(jù)資源名稱>[;<屬性名>=<屬性值>],因此,以下都是合法的jdbc:odbc名稱:
jdbc:odbc:qeor7
jdbc:odbc:wombat
jdbc:odbc:wombat;CacheSize=20;ExtensionCase=LOWER
jdbc:odbc:qeora;UID=kgh;PWD=fooey
5. 注冊(cè)子協(xié)議
驅(qū)動(dòng)程序編程員可保留某個(gè)名稱以將之用作JDBC URL的子協(xié)議名。當(dāng)DriverManager類將此名稱加到已注冊(cè)的驅(qū)動(dòng)程序清單中時(shí),為之保留該名稱的驅(qū)動(dòng)程序應(yīng)能識(shí)別該名稱并與它所標(biāo)識(shí)的數(shù)據(jù)庫(kù)建立連接。例如,odbc是為JDBC-ODBC橋而保留的。假設(shè)有個(gè)Miracle公司,它可能會(huì)將"miracle"注冊(cè)為連接到其Miracle DBMS上的JDBC驅(qū)動(dòng)程序的子協(xié)議,從而使其他人都無(wú)法使用這個(gè)名稱。
JavaSoft目前作為非正式代理負(fù)責(zé)注冊(cè)JDBC子協(xié)議名稱。要注冊(cè)某個(gè)子協(xié)議名稱,請(qǐng)發(fā)送電子郵件到下述地址:jdbc@wombat.eng.sun.com。
6. 發(fā)送SQL語(yǔ)句
連接一旦建立,就可用來(lái)向它所涉及的數(shù)據(jù)庫(kù)傳送SQL語(yǔ)句。JDBC對(duì)可被發(fā)送的SQL語(yǔ)句類型不加任何限制。這就提供了很大的靈活性,即允許使用特定的數(shù)據(jù)庫(kù)語(yǔ)句或甚至于非SQL語(yǔ)句。然而,它要求用戶自己負(fù)責(zé)確保所涉及的數(shù)據(jù)庫(kù)可以處理所發(fā)送的SQL語(yǔ)句,否則將自食其果。例如,如果某個(gè)應(yīng)用程序試圖向不支持儲(chǔ)存程序的DBMS發(fā)送儲(chǔ)存程序調(diào)用,就會(huì)失敗并將拋出異常。JDBC要求驅(qū)動(dòng)程序應(yīng)至少能提供ANSI SQL-2 Entry Level功能才可算是符合JDBC標(biāo)準(zhǔn)TM的。這意味著用戶至少可信賴這一標(biāo)準(zhǔn)級(jí)別的功能。
JDBC提供了三個(gè)類,用于向數(shù)據(jù)庫(kù)發(fā)送SQL語(yǔ)句。Connection接口中的三個(gè)方法可用于創(chuàng)建這些類的實(shí)例。下面列出這些類及其創(chuàng)建方法:
(1)Statement:由方法createStatement所創(chuàng)建。Statement對(duì)象用于發(fā)送簡(jiǎn)單的SQL語(yǔ)句。
(2)PreparedStatement:由方法prepareStatement所創(chuàng)建。PreparedStatement對(duì)象用于發(fā)送帶有一個(gè)或多個(gè)輸入?yún)?shù)(IN參數(shù))的SQL語(yǔ)句。PreparedStatement擁有一組方法,用于設(shè)置IN參數(shù)的值。執(zhí)行語(yǔ)句時(shí),這些IN參數(shù)將被送到數(shù)據(jù)庫(kù)中。PreparedStatement的實(shí)例擴(kuò)展了Statement,因此它們都包括了Statement的方法。PreparedStatement對(duì)象有可能比Statement對(duì)象的效率更高,因?yàn)樗驯活A(yù)編譯過(guò)并存放在那以供將來(lái)使用。
(3)CallableStatement:由方法prepareCall所創(chuàng)建。CallableStatement對(duì)象用于執(zhí)行SQL儲(chǔ)存程序─一組可通過(guò)名稱來(lái)調(diào)用(就象函數(shù)的調(diào)用那樣)的SQL語(yǔ)句。CallableStatement對(duì)象從PreparedStatement中繼承了用于處理IN參數(shù)的方法,而且還增加了用于處理OUT參數(shù)和INOUT參數(shù)的方法。
不過(guò)通常來(lái)說(shuō)createStatement方法用于簡(jiǎn)單的SQL語(yǔ)句(不帶參數(shù))、prepareStatement方法用于帶一個(gè)或多個(gè)IN參數(shù)的SQL語(yǔ)句或經(jīng)常被執(zhí)行的簡(jiǎn)單SQL語(yǔ)句,而prepareCall方法用于調(diào)用已儲(chǔ)存過(guò)程。
7. 事務(wù)
事務(wù)由一個(gè)或多個(gè)這樣的語(yǔ)句組成:這些語(yǔ)句已被執(zhí)行、完成并被提交或還原。當(dāng)調(diào)用方法commit或rollback時(shí),當(dāng)前事務(wù)即告就結(jié)束,另一個(gè)事務(wù)隨即開(kāi)始。缺省情況下,新連接將處于自動(dòng)提交模式。也就是說(shuō),當(dāng)執(zhí)行完語(yǔ)句后,將自動(dòng)對(duì)那個(gè)語(yǔ)句調(diào)用commit方法。這種情況下,由于每個(gè)語(yǔ)句都是被單獨(dú)提交的,因此一個(gè)事務(wù)只由一個(gè)語(yǔ)句組成。如果禁用自動(dòng)提交模式,事務(wù)將要等到commit或rollback方法被顯式調(diào)用時(shí)才結(jié)束,因此它將包括上一次調(diào)用commit或rollback方法以來(lái)所有執(zhí)行過(guò)的語(yǔ)句。對(duì)于第二種情況,事務(wù)中的所有語(yǔ)句將作為組來(lái)提交或還原。
方法commit使SQL語(yǔ)句對(duì)數(shù)據(jù)庫(kù)所做的任何更改成為永久性的,它還將釋放事務(wù)持有的全部鎖。而方法rollback將棄去那些更改。有時(shí)用戶在另一個(gè)更改生效前不想讓此更改生效。這可通過(guò)禁用自動(dòng)提交并將兩個(gè)更新組合在一個(gè)事務(wù)中來(lái)達(dá)到。如果兩個(gè)更新都是成功,則調(diào)用commit方法,從而使兩個(gè)更新結(jié)果成為永久性的;如果其中之一或兩個(gè)更新都失敗了,則調(diào)用rollback方法,以將值恢復(fù)為進(jìn)行更新之前的值。
大多數(shù)JDBC驅(qū)動(dòng)程序都支持事務(wù)。事實(shí)上,符合JDBC的驅(qū)動(dòng)程序必須支持事務(wù)。DatabaseMetaData給出的信息描述DBMS所提供的事務(wù)支持水平。
8. 事務(wù)隔離級(jí)別
如果DBMS支持事務(wù)處理,它必須有某種途徑來(lái)管理兩個(gè)事務(wù)同時(shí)對(duì)一個(gè)數(shù)據(jù)庫(kù)進(jìn)行操作時(shí)可能發(fā)生的沖突。用戶可指定事務(wù)隔離級(jí)別,以指明DBMS應(yīng)該花多大精力來(lái)解決潛在沖突。例如,當(dāng)事務(wù)更改了某個(gè)值而第二個(gè)事務(wù)卻在該更改被提交或還原前讀取該值時(shí)該怎么辦。
假設(shè)第一個(gè)事務(wù)被還原后,第二個(gè)事務(wù)所讀取的更改值將是無(wú)效的,那么是否可允許這種沖突?JDBC用戶可用以下代碼來(lái)指示DBMS允許在值被提交前讀取該值("dirty讀取"),其中con是當(dāng)前連接:
con.setTransactionIsolation(TRANSACTION_READ_UNCOMMITTED);
事務(wù)隔離級(jí)別越高,為避免沖突所花的精力也就越多。Connection接口定義了五級(jí),其中最低級(jí)別指定了根本就不支持事務(wù),而最高級(jí)別則指定當(dāng)事務(wù)在對(duì)某個(gè)數(shù)據(jù)庫(kù)進(jìn)行操作時(shí),任何其它事務(wù)不得對(duì)那個(gè)事務(wù)正在讀取的數(shù)據(jù)進(jìn)行任何更改。通常,隔離級(jí)別越高,應(yīng)用程序執(zhí)行的速度也就越慢(由于用于鎖定的資源耗費(fèi)增加了,而用戶間的并發(fā)操作減少了)。在決定采用什么隔離級(jí)別時(shí),開(kāi)發(fā)人員必須在性能需求和數(shù)據(jù)一致性需求之間進(jìn)行權(quán)衡。當(dāng)然,實(shí)際所能支持的級(jí)別取決于所涉及的DBMS的功能。
當(dāng)創(chuàng)建Connection對(duì)象時(shí),其事務(wù)隔離級(jí)別取決于驅(qū)動(dòng)程序,但通常是所涉及的數(shù)據(jù)庫(kù)的缺省值。用戶可通過(guò)調(diào)用setIsolationLevel方法來(lái)更改事務(wù)隔離級(jí)別。新的級(jí)別將在該連接過(guò)程的剩余時(shí)間內(nèi)生效。要想只改變一個(gè)事務(wù)的事務(wù)隔離級(jí)別,必須在該事務(wù)開(kāi)始前進(jìn)行設(shè)置,并在該事務(wù)結(jié)束后進(jìn)行復(fù)位。我們不提倡在事務(wù)的中途對(duì)事務(wù)隔離級(jí)別進(jìn)行更改,因?yàn)檫@將立即觸發(fā)commit方法的調(diào)用,使在此之前所作的任何更改變成永久性的。
JDBC驅(qū)動(dòng)管理內(nèi)幕是怎么樣的?
DriverManager 類是 JDBC 的管理層,作用于用戶和驅(qū)動(dòng)程序之間。它跟蹤可用的驅(qū)動(dòng)程序,并在數(shù)據(jù)庫(kù)和相應(yīng)驅(qū)動(dòng)程序之間建立連接。另外,DriverManager類也處理諸如驅(qū)動(dòng)程序登錄時(shí)間限制及登錄和跟蹤消息的顯示等事務(wù)。
對(duì)于簡(jiǎn)單的應(yīng)用程序,一般程序員需要在此類中直接使用的唯一方法是DriverManager.getConnection。正如名稱所示,該方法將建立與數(shù)據(jù)庫(kù)的連接。JDBC允許用戶調(diào)用DriverManager的方法getDriver、getDrivers和registerDriver及Driver的方法connect。但多數(shù)情況下,讓DriverManager類管理建立連接的細(xì)節(jié)為上策。
1. 跟蹤可用驅(qū)動(dòng)程序
DriverManager類包含一列Driver類,它們已通過(guò)調(diào)用方法DriverManager.registerDriver對(duì)自己進(jìn)行了注冊(cè)。所有Driver類都必須包含有一個(gè)靜態(tài)部分。它創(chuàng)建該類的實(shí)例,然后在加載該實(shí)例時(shí)DriverManager類進(jìn)行注冊(cè)。這樣,用戶正常情況下將不會(huì)直接調(diào)用DriverManager.registerDriver;而是在加載驅(qū)動(dòng)程序時(shí)由驅(qū)動(dòng)程序自動(dòng)調(diào)用。加載Driver類,然后自動(dòng)在DriverManager中注冊(cè)的方式有兩種:
(1)調(diào)用方法Class.forName
這將顯式地加載驅(qū)動(dòng)程序類。由于這與外部設(shè)置無(wú)關(guān),因此推薦使用這種加載驅(qū)動(dòng)程序的方法。以下代碼加載類acme.db.Driver:Class.forName("acme.db.Driver")。
如果將acme.db.Driver編寫(xiě)為加載時(shí)創(chuàng)建實(shí)例,并調(diào)用以該實(shí)例為參數(shù)的DriverManager.registerDriver(本該如此),則它在DriverManager的驅(qū)動(dòng)程序列表中,并可用于創(chuàng)建連接。
(2)將驅(qū)動(dòng)程序添加到Java.lang.System的屬性jdbc.drivers中
這是一個(gè)由DriverManager類加載的驅(qū)動(dòng)程序類名的列表,由冒號(hào)分隔:初始化DriverManager類時(shí),它搜索系統(tǒng)屬性jdbc.drivers,如果用戶已輸入了一個(gè)或多個(gè)驅(qū)動(dòng)程序,則DriverManager類將試圖加載它們。以下代碼說(shuō)明程序員如何在~/.hotJava/properties中輸入三個(gè)驅(qū)動(dòng)程序類(啟動(dòng)時(shí),HotJava將把它加載到系統(tǒng)屬性列表中):
jdbc.drivers=foo.bah.Driver:wombat.sql.Driver:bad.test.ourDriver;
對(duì)DriverManager方法的第一次調(diào)用將自動(dòng)加載這些驅(qū)動(dòng)程序類。注意:加載驅(qū)動(dòng)程序的第二種方法需要持久的預(yù)設(shè)環(huán)境。如果對(duì)這一點(diǎn)不能保證,則調(diào)用方法Class.forName顯式地加載每個(gè)驅(qū)動(dòng)程序就顯得更為安全。這也是引入特定驅(qū)動(dòng)程序的方法,因?yàn)橐坏〥riverManager類被初始化,它將不再檢查jdbc.drivers屬性列表。
在以上兩種情況中,新加載的Driver類都要通過(guò)調(diào)用DriverManager.registerDriver類進(jìn)行自我注冊(cè)。如上所述,加載類時(shí)將自動(dòng)執(zhí)行這一過(guò)程。
由于安全方面的原因,JDBC管理層將跟蹤哪個(gè)類加載器提供哪個(gè)驅(qū)動(dòng)程序。這樣,當(dāng)DriverManager類打開(kāi)連接時(shí),它僅使用本地文件系統(tǒng)或與發(fā)出連接請(qǐng)求的代碼相同的類加載器提供的驅(qū)動(dòng)程序。
2. 建立連接
加載Driver類并在DriverManager類中注冊(cè)后,它們即可用來(lái)與數(shù)據(jù)庫(kù)建立連接。當(dāng)調(diào)用DriverManager.getConnection方法發(fā)出連接請(qǐng)求時(shí),DriverManager將檢查每個(gè)驅(qū)動(dòng)程序,查看它是否可以建立連接。
有時(shí)可能有多個(gè)JDBC驅(qū)動(dòng)程序可以與給定的URL連接。例如,與給定遠(yuǎn)程數(shù)據(jù)庫(kù)連接時(shí),可以使用JDBC-ODBC橋驅(qū)動(dòng)程序、JDBC到通用網(wǎng)絡(luò)協(xié)議驅(qū)動(dòng)程序或數(shù)據(jù)庫(kù)廠商提供的驅(qū)動(dòng)程序。在這種情況下測(cè)試驅(qū)動(dòng)程序的順序至關(guān)重要,因?yàn)镈riverManager將使用它所找到的第一個(gè)可以成功連接到給定URL的驅(qū)動(dòng)程序。
首先DriverManager試圖按注冊(cè)的順序使用每個(gè)驅(qū)動(dòng)程序(jdbc.drivers中列出的驅(qū)動(dòng)程序總是先注冊(cè))。它將跳過(guò)代碼不可信任的驅(qū)動(dòng)程序,除非加載它們的源與試圖打開(kāi)連接的代碼的源相同。它通過(guò)輪流在每個(gè)驅(qū)動(dòng)程序上調(diào)用方法Driver.connect,并向它們傳遞用戶開(kāi)始傳遞給方法DriverManager.getConnection的URL來(lái)對(duì)驅(qū)動(dòng)程序進(jìn)行測(cè)試,然后連接第一個(gè)認(rèn)出該URL的驅(qū)動(dòng)程序。這種方法初看起來(lái)效率不高,但由于不可能同時(shí)加載數(shù)十個(gè)驅(qū)動(dòng)程序,因此每次連接實(shí)際只需幾個(gè)過(guò)程調(diào)用和字符串比較。
以下代碼是通常情況下用驅(qū)動(dòng)程序(例如JDBC-ODBC橋驅(qū)動(dòng)程序)建立連接所需所有步驟的示例:
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");//加載驅(qū)動(dòng)程序
String url = "jdbc:odbc:fred";
DriverManager.getConnection(url,"userID","passwd");
如何利用JDBC發(fā)送SQL語(yǔ)句?
Statement對(duì)象用于將SQL語(yǔ)句發(fā)送到數(shù)據(jù)庫(kù)中。實(shí)際上有三種Statement對(duì)象,它們都作為在給定連接上執(zhí)行SQL語(yǔ)句的包容器:Statement、PreparedStatement(它從Statement繼承而來(lái))和CallableStatement(它從PreparedStatement繼承而來(lái))。它們都專用于發(fā)送特定類型的SQL語(yǔ)句:Statement對(duì)象用于執(zhí)行不帶參數(shù)的簡(jiǎn)單SQL語(yǔ)句;PreparedStatement對(duì)象用于執(zhí)行帶或不帶IN參數(shù)的預(yù)編譯SQL語(yǔ)句;CallableStatement對(duì)象用于執(zhí)行對(duì)數(shù)據(jù)庫(kù)已存儲(chǔ)過(guò)程的調(diào)用。
Statement接口提供了執(zhí)行語(yǔ)句和獲取結(jié)果的基本方法;PreparedStatement接口添加了處理IN參數(shù)的方法;而CallableStatement添加了處理OUT參數(shù)的方法。
1. 創(chuàng)建Statement對(duì)象
建立了到特定數(shù)據(jù)庫(kù)的連接之后,就可用該連接發(fā)送SQL語(yǔ)句。Statement對(duì)象用Connection的方法createStatement創(chuàng)建,如下列代碼段中所示:
Connection con = DriverManager.getConnection(url,"sunny","");
Statement stmt = con.createStatement();
為了執(zhí)行Statement對(duì)象,被發(fā)送到數(shù)據(jù)庫(kù)的SQL語(yǔ)句將被作為參數(shù)提供給Statement的方法:
ResultSet rs = stmt.executeQuery("SELECT a,b,c FROM Table2");
2. 使用Statement對(duì)象執(zhí)行語(yǔ)句
Statement接口提供了三種執(zhí)行SQL語(yǔ)句的方法:executeQuery、executeUpdate和execute。使用哪一個(gè)方法由SQL語(yǔ)句所產(chǎn)生的內(nèi)容決定。
方法executeQuery用于產(chǎn)生單個(gè)結(jié)果集的語(yǔ)句,例如SELECT語(yǔ)句。方法executeUpdate用于執(zhí)行INSERT、UPDATE或DELETE語(yǔ)句以及SQL DDL(數(shù)據(jù)定義語(yǔ)言)語(yǔ)句,例如CREATE TABLE和DROP TABLE。INSERT、UPDATE或DELETE語(yǔ)句的效果是修改表中零行或多行中的一列或多列。executeUpdate的返回值是一個(gè)整數(shù),指示受影響的行數(shù)(即更新計(jì)數(shù))。對(duì)于CREATE TABLE或DROP TABLE等不操作行的語(yǔ)句,executeUpdate的返回值總為零。
執(zhí)行語(yǔ)句的所有方法都將關(guān)閉所調(diào)用的Statement對(duì)象的當(dāng)前打開(kāi)結(jié)果集(如果存在)。這意味著在重新執(zhí)行Statement對(duì)象之前,需要完成對(duì)當(dāng)前ResultSet對(duì)象的處理。應(yīng)注意,繼承了Statement接口中所有方法的PreparedStatement接口都有自己的executeQuery、executeUpdate和execute方法。Statement對(duì)象本身不包含SQL語(yǔ)句,因而必須給Statement.execute方法提供SQL語(yǔ)句作為參數(shù)。PreparedStatement對(duì)象并不需要SQL語(yǔ)句作為參數(shù)提供給這些方法,因?yàn)樗鼈円呀?jīng)包含預(yù)編譯SQL語(yǔ)句。
CallableStatement對(duì)象繼承這些方法的PreparedStatement形式。對(duì)于這些方法的PreparedStatement或CallableStatement版本,使用查詢參數(shù)將拋出SQLException。
3. 語(yǔ)句完成
當(dāng)連接處于自動(dòng)提交模式時(shí),其中所執(zhí)行的語(yǔ)句在完成時(shí)將自動(dòng)提交或還原。語(yǔ)句在已執(zhí)行且所有結(jié)果返回時(shí),即認(rèn)為已完成。對(duì)于返回一個(gè)結(jié)果集的executeQuery方法,在檢索完ResultSet對(duì)象的所有行時(shí)該語(yǔ)句完成。對(duì)于方法executeUpdate,當(dāng)它執(zhí)行時(shí)語(yǔ)句即完成。但在少數(shù)調(diào)用方法execute的情況中,在檢索所有結(jié)果集或它生成的更新計(jì)數(shù)之后語(yǔ)句才完成。
有些DBMS將已存儲(chǔ)過(guò)程中的每條語(yǔ)句視為獨(dú)立的語(yǔ)句;而另外一些則將整個(gè)過(guò)程視為一個(gè)復(fù)合語(yǔ)句。在啟用自動(dòng)提交時(shí),這種差別就變得非常重要,因?yàn)樗绊懯裁磿r(shí)候調(diào)用commit方法。在前一種情況中,每條語(yǔ)句單獨(dú)提交;在后一種情況中,所有語(yǔ)句同時(shí)提交。
4. 關(guān)閉Statement對(duì)象
Statement對(duì)象將由Java垃圾收集程序自動(dòng)關(guān)閉。而作為一種好的編程風(fēng)格,應(yīng)在不需要Statement對(duì)象時(shí)顯式地關(guān)閉它們。這將立即釋放DBMS資源,有助于避免潛在的內(nèi)存問(wèn)題。
5. 使用方法execute
execute方法應(yīng)該僅在語(yǔ)句能返回多個(gè)ResultSet對(duì)象、多個(gè)更新計(jì)數(shù)或ResultSet對(duì)象與更新計(jì)數(shù)的組合時(shí)使用。當(dāng)執(zhí)行某個(gè)已存儲(chǔ)過(guò)程或動(dòng)態(tài)執(zhí)行未知SQL字符串(即應(yīng)用程序程序員在編譯時(shí)未知)時(shí),有可能出現(xiàn)多個(gè)結(jié)果的情況,盡管這種情況很少見(jiàn)。例如,用戶可能執(zhí)行一個(gè)已存儲(chǔ)過(guò)程,并且該已存儲(chǔ)過(guò)程可執(zhí)行更新,然后執(zhí)行選擇,再進(jìn)行更新,再進(jìn)行選擇,等等。通常使用已存儲(chǔ)過(guò)程的人應(yīng)知道它所返回的內(nèi)容。
因?yàn)榉椒╡xecute處理非常規(guī)情況,所以獲取其結(jié)果需要一些特殊處理并不足為怪。例如,假定已知某個(gè)過(guò)程返回兩個(gè)結(jié)果集,則在使用方法execute執(zhí)行該過(guò)程后,必須調(diào)用方法getResultSet獲得第一個(gè)結(jié)果集,然后調(diào)用適當(dāng)?shù)膅etXXX方法獲取其中的值。要獲得第二個(gè)結(jié)果集,需要先調(diào)用getMoreResults方法,然后再調(diào)用getResultSet方法。如果已知某個(gè)過(guò)程返回兩個(gè)更新計(jì)數(shù),則首先調(diào)用方法getUpdateCount,然后調(diào)用getMoreResults,并再次調(diào)用getUpdateCount。
對(duì)于不知道返回內(nèi)容,則情況更為復(fù)雜。如果結(jié)果是ResultSet對(duì)象,則方法execute返回true;如果結(jié)果是Javaint,則返回false。如果返回int,則意味著結(jié)果是更新計(jì)數(shù)或執(zhí)行的語(yǔ)句是DL命令。在調(diào)用方法execute之后要做的第一件事情是調(diào)用getResultSet或getUpdateCount。調(diào)用方法getResultSet可以獲得兩個(gè)或多個(gè)ResultSet對(duì)象中第一個(gè)對(duì)象;或調(diào)用方法getUpdateCount可以獲得兩個(gè)或多個(gè)更新計(jì)數(shù)中第一個(gè)更新計(jì)數(shù)的內(nèi)容。
當(dāng)SQL語(yǔ)句的結(jié)果不是結(jié)果集時(shí),則方法getResultSet將返回null。這可能意味著結(jié)果是一個(gè)更新計(jì)數(shù)或沒(méi)有其它結(jié)果。在這種情況下,判斷null真正含義的唯一方法是調(diào)用方法getUpdateCount,它將返回一個(gè)整數(shù)。這個(gè)整數(shù)為調(diào)用語(yǔ)句所影響的行數(shù);如果為-1則表示結(jié)果是結(jié)果集或沒(méi)有結(jié)果。如果方法getResultSet已返回null(表示結(jié)果不是ResultSet對(duì)象),則返回值-1表示沒(méi)有其它結(jié)果。也就是說(shuō),當(dāng)下列條件為真時(shí)表示沒(méi)有結(jié)果(或沒(méi)有其它結(jié)果):
((stmt.getResultSet()==null)&&(stmt.getUpdateCount()==-1))
如果已經(jīng)調(diào)用方法getResultSet并處理了它返回的ResultSet對(duì)象,則有必要調(diào)用方法getMoreResults以確定是否有其它結(jié)果集或更新計(jì)數(shù)。如果getMoreResults返回true,則需要再次調(diào)用getResultSet來(lái)檢索下一個(gè)結(jié)果集。如上所述,如果getResultSet返回null,則需要調(diào)用getUpdateCount來(lái)檢查null是表示結(jié)果為更新計(jì)數(shù)還是表示沒(méi)有其它結(jié)果。
當(dāng)getMoreResults返回false時(shí),它表示該SQL語(yǔ)句返回一個(gè)更新計(jì)數(shù)或沒(méi)有其它結(jié)果。因此需要調(diào)用方法getUpdateCount來(lái)檢查它是哪一種情況。在這種情況下,當(dāng)下列條件為真時(shí)表示沒(méi)有其它結(jié)果:
((stmt.getMoreResults()==false)&&(stmt.getUpdateCount()==-1))