數(shù)據(jù)庫的種類
由于 PC 已成為主要的辦公工具,因此,已開發(fā)出在 PC 上的大量流行的數(shù)據(jù)庫,這些數(shù)據(jù)庫都是可以自管理的。它們包括初級數(shù)據(jù)庫,如 MicrosoftWorks ,還包括更為復雜的數(shù)據(jù)庫,如 Approach 、 dBase 、 Borland Paradox 、 Microsoft Access 和 FoxBase 。 另一類PC數(shù)據(jù)庫包括那些可由許多PC客戶機通過服務器訪問的數(shù)據(jù)庫。其中包括IBM DB/2、Microsoft SQL Server、Oracle、Sybase、SQLBase和XDB。所有這些數(shù)據(jù)庫產品都支持多種相對類似的SQL方言,因此,所有數(shù)據(jù)庫最初看起來好象可以互換。當然,它們 不能互換的原因是每種數(shù)據(jù)庫都有不同的性能特征,而且每一種都有不同的用戶界面和編程接口。您可能會想,既然它們都支持SQL,對它們進行的編程也應該相似,但這是絕對錯誤的,因為每種數(shù)據(jù)庫都使用其自己方式接收SQL查詢,并使用其自己的方式返回結果。這就自然引出了一種新一代的標準:ODBC
ODBC
如果我們能夠以某種方式編寫不依賴于特定廠商的數(shù)據(jù)庫的代碼,并且能夠不改變自己的調用程序即可從這些數(shù)據(jù)庫中得到相同的結果,那將是一件很好的事。如果我們可以僅為所有這些數(shù)據(jù)庫編寫一些封裝,使它們具有相似的編程接口,這種對數(shù)據(jù)庫編程獨立于供應商的特性將很容易實現(xiàn)。 Microsoft于1992年首先嘗試了這一技巧,該公司發(fā)布了一個規(guī)范,稱為對象數(shù)據(jù)庫連接性。這被認為是在Windows環(huán)境下連接所有數(shù)據(jù)庫的答案。與所有軟件的第一個版本相同,它也經歷了一些發(fā)展的困擾,在1994年推出了另一個版本,該版本運行速度更快,而且更為穩(wěn)定。它也是第一個32位的版本。另外,ODBC開始向Windows之外的其它平臺發(fā)展,到目前為止,它在PC和工作站領域已十分普遍。幾乎每個主要的數(shù)據(jù)庫廠商都提供ODBC驅動程序。
然而,ODBC并不是我們最初想象的靈丹妙藥。許多數(shù)據(jù)庫廠商都將ODBC作為其標準接口之外的“備選接口”,而且對ODBC的編程微不足道。與其它Windows編程一樣,它包括句柄、指針和選項,使其難以掌握。最后一點,ODBC不是中立的標準。它由Microsoft公司開發(fā),并由該公司不斷改進,而微軟同時也推出了我們所有人所使用的極具競爭性的軟件平臺,這使得ODBC的未來難以預測。
什么是JDBC?
JDBC 是一組首字母縮寫,曾經代表 “Java DataBaseConnectivity” ,但現(xiàn)在它本身已成為一個商標符號。它是對 ODBC API 進行的一種面向對象的封裝和重新設計,它易于學習和使用,并且它真正能夠使您編寫不依賴廠商的代碼,用以查詢和操縱數(shù)據(jù)庫。盡管它與所有 Java API 一樣,都是面向對象的,但它并不是很高級別的對象集,在本章的剩余部分,我們將提出更高級別的方法。 除Microsoft之外,多數(shù)廠商都采用了JDBC,并為其數(shù)據(jù)庫提供了JDBC驅動程序;這使您可輕松地真正編寫幾乎完全不依賴數(shù)據(jù)庫的代碼。另外,JavaSoft和Intersolv已開發(fā)了一種稱為JDBC-ODBCBridge的產品,可使您連接還沒有直接的JDBC驅動程序的數(shù)據(jù)庫。支持JDBC的所有數(shù)據(jù)庫必須至少可以支持SQL-92標準。這在很大程度上實現(xiàn)了跨數(shù)據(jù)庫和平臺的可移植性。
安裝和使用JDBC
JDBC 的類都被歸到 java.sql
包中,在安裝 Java JDK 1.1 或更高版本時會自動安裝。然而,如果您想使用 JDBC-ODBC 橋,還必須安裝兩個另外的程序包。首先,如果您使用 Windows95 ,則必須將您的 ODBC 驅動程序升級為 32 位驅動程序,您可從 Microsoft 的網站下載。這個驅動程序在 Microsoft 的網站上很難找到;請搜索 DataAcc.exe 并進行下載和安裝。
JDBC-ODBC驅動程序可從Sun的Java網站( http://java.sun.com)輕松地找到并下載。在您擴充并安裝了這個驅動程序后,必須執(zhí)行下列步驟:
- 將
\jdbc-odbc\classes;
路徑添加到您的PATH環(huán)境變量中。 - 將
\jdbc-odbc\classes;
路徑添加到您的CLASSPATH環(huán)境變量中。 - 在Windows 95環(huán)境下,將它們放入autoexec.bat文件中,重新引導,以使所有設置生效。
- 在Windows NT環(huán)境下,將它們添加到“控制面板”中“系統(tǒng)”對象的“環(huán)境”選項卡中,退出并重新登錄,以使其生效。
JDBC驅動程序的類型
Java 程序連接數(shù)據(jù)庫的方法實際上有四種: - JDBC-ODBC橋和ODBC驅動程序--在這種方式下,這是一個本地解決方案,因為ODBC驅動程序和橋代碼必須出現(xiàn)在用戶的每臺機器中。從根本上說這是一個臨時解決方案。
-
- 本機代碼和Java驅動程序--它用另一個本地解決方案(該平臺上的Java可調用的本機代碼)取代 ODBC 和 JDBC-ODBC 橋。
-
- JDBC網絡的純Java驅動程序--由Java驅動程序翻譯的JDBC形成傳送給服務器的獨立協(xié)議。然后,服務器可連接任何數(shù)量的數(shù)據(jù)庫。這種方法使您可能從客戶機Applet中調用服務器,并將結果返回到您的Applet。在這種情況下,中間件軟件提供商可提供服務器。
-
- 本機協(xié)議Java驅動程序-- Java驅動程序直接轉換為該數(shù)據(jù)庫的協(xié)議并進行調用。這種方法也可以通過網絡使用,而且可以在Web瀏覽器的Applet中顯示結果。在這種情況下,每個數(shù)據(jù)庫廠商將提供驅動程序。
如果您希望編寫代碼來處理 PC 客戶機數(shù)據(jù)庫,如 dBase 、 Foxbase 或 Access ,則您可能會使用第一種方法,并且擁有用戶機器上的所有代碼。更大的客戶機 - 服務器數(shù)據(jù)庫產品(如 IBM 的 DB2 )已提供了第 3 級別的驅動程序。
兩層模型和三層模型
當數(shù)據(jù)庫和查詢它的應用程序在同一臺機器上,而且沒有服務器代碼的干預時,我們將生成的程序稱為 兩層模型。一層是應用程序,而另一層是數(shù)據(jù)庫。在 JDBC-ODBC 橋系統(tǒng)中通常是這種情況。 當一個應用程序或applet調用服務器,服務器再去調用數(shù)據(jù)庫時,我們稱其為 三層模型。當您調用稱為“服務器”的程序時通常是這種情況。
編寫JDBC代碼訪問數(shù)據(jù)庫
現(xiàn)在,我們將開始看一下如何編寫 Java 程序來訪問數(shù)據(jù)庫。我們要使用的數(shù)據(jù)庫是一個稱為 groceries.mdb 的 Microsoft Access 數(shù)據(jù)庫。此數(shù)據(jù)庫中的數(shù)據(jù)由三個本地雜貨店中一些常見商品的價格組成。食品表如下所示:
FoodKey | FoodName |
1 | Apples |
2 | Oranges |
3 | Hamburger |
4 | Butter |
5 | Milk |
6 | Cola |
7 | Greenbeans |
雜貨店表如下所示:
StoreKey | StoreName |
1 | Stop andShop |
2 | VillageMarket |
3 | Waldbaum‘s |
雜貨店定價表僅由這三個表格中的鍵值和價格組成:
FSKey | StoreKey | FoodKey | Price |
1 | 1 | 1 | $0.27 |
2 | 2 | 1 | $0.29 |
3 | 3 | 1 | $0.33 |
4 | 1 | 2 | $0.36 |
5 | 2 | 2 | $0.29 |
6 | 3 | 2 | $0.47 |
7 | 1 | 3 | $1.98 |
8 | 2 | 3 | $2.45 |
9 | 3 | 3 | $2.29 |
10 | 1 | 4 | $2.39 |
11 | 2 | 4 | $2.99 |
12 | 3 | 4 | $3.29 |
13 | 1 | 5 | $1.98 |
14 | 2 | 5 | $1.79 |
15 | 3 | 5 | $1.89 |
16 | 1 | 6 | $2.65 |
17 | 2 | 6 | $3.79 |
18 | 3 | 6 | $2.99 |
19 | 1 | 7 | $2.29 |
20 | 2 | 7 | $2.19 |
21 | 3 | 7 | $1.99 |
用ODBC注冊您的數(shù)據(jù)庫
在 Windows 95 或 NT 環(huán)境下訪問 ODBC 數(shù)據(jù)庫之前,必須使用控制面板中的 ODBC 驅動程序對它進行注冊。在 Windows 95 環(huán)境下,就是 “ 控制面板 ” 程序中的 ODBC 圖標。在 Windows NT 環(huán)境下,您會在 “ 開始 ” 菜單中找到此程序。(如果找不到,您需要安裝上述的 ODBC 驅動程序,即 WX1350.exe )。
雙擊ODBC圖標,然后單擊“添加”,如圖1所示。然后選擇數(shù)據(jù)庫驅動程序(此處使用MicrosoftAccess),然后單擊“確定”。在“數(shù)據(jù)源名”和“描述”中分別鍵入數(shù)據(jù)源名稱(Groceries)和數(shù)據(jù)庫說明(Groceryprices)(這兩項都不需要和文件名相關),然后單擊“選取”,找到數(shù)據(jù)庫,并選擇該數(shù)據(jù)庫。找到該數(shù)據(jù)庫后,屏幕將如圖2所示。單擊“確定”,然后單擊“關閉”來關閉面板。
圖1:ODBC控制面板設置屏幕。
圖2:在ODBC控制面板中選擇數(shù)據(jù)庫和說明。
連接數(shù)據(jù)庫
所有與數(shù)據(jù)庫有關的對象和方法都在 java.sql 包中,因此在使用 JDBC 的程序中必須加入 "import java.sql.* "
。 JDBC 要連接 ODBC 數(shù)據(jù)庫,您必須首先加載 JDBC-ODBC 橋驅動程序
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); |
該語句加載驅動程序,并創(chuàng)建該類的一個實例。然后,要連接一個特定的數(shù)據(jù)庫,您必須創(chuàng)建 Connect 類的一個實例,并使用 URL 語法連接數(shù)據(jù)庫。 String url = "jdbc:odbc:Grocery prices"; Connection con = DriverManager.getConnection(url); |
請注意,您使用的數(shù)據(jù)庫名是您在 ODBC 設置面板中輸入的 “ 數(shù)據(jù)源 ” 名稱。
URL語法可能因數(shù)據(jù)庫類型的不同而變化極大。
jdbc: subprotocol: subname |
第一組字符代表連接 協(xié)議,并且始終是 jdbc
。還可能有一個 子協(xié)議,在此處,子協(xié)議被指定為 odbc
。它規(guī)定了一類數(shù)據(jù)庫的連通性機制。如果您要連接其它機器上的數(shù)據(jù)庫服務器,可能也要指定該機器和一個子目錄: jdbc:bark//doggie/elliott |
最后,您可能要指定用戶名和口令,作為連接字符串的一部分: jdbc:bark//doggie/elliot;UID=GoodDog;PWD=woof |
訪問數(shù)據(jù)庫
一旦連接到數(shù)據(jù)庫,就可以請求表名以及表列的名稱和內容等信息,而且您可以運行 SQL 語句來查詢數(shù)據(jù)庫或者添加或修改其內容??捎脕韽臄?shù)據(jù)庫中獲取信息的對象有:
DatabaseMetaData | 有關整個數(shù)據(jù)庫的信息:表名、表的索引、數(shù)據(jù)庫產品的名稱和版本、數(shù)據(jù)庫支持的操作。 |
ResultSet | 關于某個表的信息或一個查詢的結果。您必須逐行訪問數(shù)據(jù)行,但是您可以任何順序訪問列。 |
ResultSetMetaData | 有關ResultSet中列的名稱和類型的信息。 |
盡管每個對象都有大量的方法讓您獲得數(shù)據(jù)庫元素的極為詳細的信息,但在每個對象中都有幾種主要的方法使您可獲得數(shù)據(jù)的最重要信息。然而,如果您希望看到比此處更多的信息,建議您學習文檔以獲得其余方法的說明。
ResultSet
ResultSet 對象是 JDBC 中最重要的單個對象。從本質上講,它是對一個一般寬度和未知長度的表的一種抽象。幾乎所有的方法和查詢都將數(shù)據(jù)作為 ResultSet 返回。 ResultSet 包含任意數(shù)量的命名列,您可以按名稱訪問這些列。它還包含一個或多個行,您可以按順序自上而下逐一訪問。在您使用 ResultSet 之前,必須查詢它包含多少個列。此信息存儲在 ResultSetMetaData 對象中。
// 從元數(shù)據(jù)中獲得列數(shù) ResultSetMetaData rsmd; rsmd = results.getMetaData(); numCols = rsmd.getColumnCount(); |
當您獲得一個ResultSet時,它正好指向第一行之前的位置。您可以使用 next()
方法得到其他每一行,當沒有更多行時,該方法會返回 false
。由于從數(shù)據(jù)庫中獲取數(shù)據(jù)可能會導致錯誤,您必須始終將結果集處理語句包括在一個 try
塊中。
try { rsmd = results.getMetaData(); numCols = rsmd.getColumnCount(); boolean more = results.next(); while (more) { for (i = 1; i <= numCols; i++) System.out.print(results.getString(i)+" "); System.out.println(); more = results.next(); } results.close(); } catch(Exception e) {System.out.println(e.getMessage());} |
您可以多種形式獲取 ResultSet 中的數(shù)據(jù),這取決于每個列中存儲的數(shù)據(jù)類型。另外,您可以按列序號或列名獲取列的內容。請注意,列序號從 1 開始,而不是從 0 開始。 ResultSet 對象的一些最常用方法如下所示。
getInt(int); | 將序號為 int 的列的內容作為整數(shù)返回。 |
getInt(String); | 將名稱為 String 的列的內容作為整數(shù)返回。 |
getFloat(int); | 將序號為 int 的列的內容作為一個 float 型數(shù)返回。 |
g<tt>etFloat(String);</tt> | 將名稱為 String 的列的內容作為 float 型數(shù)返回。 |
getDate(int); | 將序號為 int 的列的內容作為日期返回。 |
getDate(String); | 將名稱為 String 的列的內容作為日期返回。 |
next(); | 將行指針移到下一行。如果沒有剩余行,則返回 false 。 |
close(); | 關閉結果集。 |
getMetaData(); | 返回 ResultSetMetaData 對象。 |
ResultSetMetaData
您使用 getMetaData()
方法從 ResultSet
中獲取 ResultSetMetaData
對象。您可以使用此對象獲得列的數(shù)目和類型以及每一列的名稱。
| 返回 ResultSet 中的列數(shù)。 |
getColumnName(int); | 返回列序號為 int 的列名。 |
getColumnLabel(int); | 返回此列暗含的標簽。 |
isCurrency(int); | 如果此列包含帶有貨幣單位的一個數(shù)字,則返回 true 。 |
isReadOnly(int); | 如果此列為只讀,則返回 true 。 |
isAutoIncrement(int); | 如果此列自動遞增,則返回 true 。這類列通常為鍵,而且始終是只讀的。 |
getColumnType(int); | 返回此列的SQL數(shù)據(jù)類型。這些數(shù)據(jù)類型包括 BIGINT BINARY BIT CHAR DATE DECIMAL DOUBLE FLOAT INTEGER LONGVARBINARY LONGVARCHAR | NULL NUMERIC OTHER REAL SMALLINT TIME TIMESTAMP TINYINT VARBINARY VARCHAR | |
DatabaseMetaData
DatabaseMetaData
對象可為您提供整個數(shù)據(jù)庫的信息。您主要用它獲取數(shù)據(jù)庫中表的名稱,以及表中列的名稱。由于不同的數(shù)據(jù)庫支持不同的 SQL 變體,因此,也有多種方法查詢數(shù)據(jù)庫支持哪些 SQL 方法。
getCatalogs() | 返回該數(shù)據(jù)庫中的信息目錄列表。使用 JDBC-ODBC Bridge 驅動程序,您可以獲得用 ODBC 注冊的數(shù)據(jù)庫列表。這很少用于 JDBC-ODBC 數(shù)據(jù)庫。 |
getTables(catalog, schema, tableNames, columnNames) | 返回表名與 tableNames 相符而且列名與 columnNames 相符的所有表的說明。 |
getColumns(catalog, schema, tableNames,columnNames) | 返回表名與 tableNames 相符而且列名與 columnNames 相符的所有表列說明。 |
getURL(); | 獲得您所連接的URL名稱。 |
getDriverName(); | 獲得您所連接的數(shù)據(jù)庫驅動程序的名稱。 |
獲取有關表的信息
您可以使用 DataBaseMetaData 的 getTables()
方法來獲取數(shù)據(jù)庫中表的信息。這個方法有如下 4 個 String 參數(shù):
results = dma.getTables(catalog, schema, tablemask, types[]); |
其中參數(shù)的意義是:
catalog | 要在其中查找表名的目錄名。對于 JDBC-ODBC 數(shù)據(jù)庫以及許多其他數(shù)據(jù)庫而言,可將其設置為 null。這些數(shù)據(jù)庫的目錄項實際上是它在文件系統(tǒng)中的絕對路徑名稱。 |
schema | 要包括的數(shù)據(jù)庫“方案”。許多數(shù)據(jù)庫不支持方案,而對另一些數(shù)據(jù)庫而言,它代表數(shù)據(jù)庫所有者的用戶名。一般將它設置為 null 。 |
tablemask | 一個掩碼,用來描述您要檢索的表的名稱。如果您希望檢索所有表名,則將其設為通配符 % 。 請注意, SQL 中的通配符是 %符號,而不是一般PC用戶的*符號。 |
types[] | 這是描述您要檢索的表的類型的String數(shù)組。數(shù)據(jù)庫中通常包括許多用于內部處理的表,而對作為用戶的您沒什么價值。如果它是空值,則您會得到所有這些表。如果您將其設為包含字符串“ TABLES ”的單元素數(shù)組,您將僅獲得對用戶有用的表格。 |
用于從數(shù)據(jù)庫中獲取表名的簡單代碼相當于獲取 DatabaseMetaData
對象,并從其中檢索表名:
con = DriverManager.getConnection(url); //獲取數(shù)據(jù)庫的元數(shù)據(jù) dma =con.getMetaData(); //將數(shù)據(jù)庫中的表的名稱轉儲出來 String[] types = new String[1]; types[0] = "TABLES"; //設置查詢類型 //請注意通配符是 % 符號(而不是 “*”) results = dma.getTables(null, null, "%", types); |
然后,我們可以打印出表名,正如我們上面所做的那樣:
boolean more = results.next(); while (more) { for (i = 1; i <= numCols; i++) System.out.print(results.getString(i)+" "); System.out.println(); more = results.next(); } |
如前文所述,將所有代碼包括在 try
塊中。
執(zhí)行SQL查詢
我們已經理解了 JDBC 的基本對象,現(xiàn)在就可以執(zhí)行 SQL 查詢了。查詢是作為 Statement
對象的方法執(zhí)行的,您很容易從 Connection
對象獲得 Statement
對象:
String query = "SELECT FoodName FROM Food;"; ResultSet results; try { Statement stmt = con.createStatement(); results = stmt.executeQuery(query); } catch (Exception e) {System.out.println("query exception");} |
請注意,這個簡單的查詢返回 Food 表中的整個 FoodName 列。您使用像這樣的簡單查詢獲取整個列的內容。請注意,查詢的查詢本身是一個 ResultSet ,您可以用我們上面剛討論過的方法對它進行處理。
打印ResultSet
因為我們總是要從 ResultSets 中打印數(shù)據(jù),我們可以設計一種簡單的方法,將整個 ResultSet 轉儲出來,包括表名稱元數(shù)據(jù)。該子程序如下所示:
private void dumpResults(String head) { // 這是打印列標頭和每列的內容的 // 通用方法 System.out.println(head); try { // 從元數(shù)據(jù)中獲取列數(shù) rsmd = results.getMetaData(); numCols = rsmd.getColumnCount(); // 打印列名 for (i = 1; i<= numCols; i++) System.out.print(rsmd.getColumnName(i)+" "); System.out.println(); // 打印列內容 boolean more = results.next(); while (more) { for (i = 1; i <= numCols; i++) System.out.print(results.getString(i)+" "; System.out.println(); more = results.next(); } } catch(Exception e) {System.out.println(e.getMessage());} } |
一個簡單的JDBC程序
我們已經學習了 JDBC 的所有基本功能,現(xiàn)在我們可以編寫一個簡單的程序,該程序打開數(shù)據(jù)庫,打印它的表名以及某一表列的內容,然后對該數(shù)據(jù)庫執(zhí)行查詢。此程序如下所示:
import java.net.URL; import java.sql.*; import java.util.*; class JdbcOdbc_test { ResultSet results; ResultSetMetaData rsmd; DatabaseMetaData dma; Connection con; int numCols, i; //-- public JdbcOdbc_test() { String url = "jdbc:odbc:Grocery prices"; String query = "SELECT DISTINCTROW FoodName FROM Food " + "WHERE (FoodName like ‘C%‘);"; try { // 加載 JDBC-ODBC 橋驅動程序 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); //連接數(shù)據(jù)庫 con = DriverManager.getConnection(url); //獲取數(shù)據(jù)庫的元數(shù)據(jù) dma =con.getMetaData(); System.out.println("Connected to:"+dma.getURL()); System.out.println("Driver "+dma.getDriverName()); //將數(shù)據(jù)庫中的表的名稱轉儲出來 String[] types = new String[1]; types[0] = "TABLES"; //請注意通配符是 % 符號(而不是“*”) results = dma.getTables(null, null, "%", types); dumpResults("--Tables--"); results.close(); } catch (Exception e) {System.out.println(e);} //獲取表列的名稱 System.out.println("--Column Names--"); try { results = dma.getColumns(null, null, "FoodPrice", null); ResultSetMetaData rsmd = results.getMetaData(); int numCols = rsmd.getColumnCount(); while (results.next() ) String cname = results.getString("COLUMN_NAME"); System.out.print(cname + " "); System.out.println(); results.close(); } catch (Exception e) {System.out.println(e);} //列出一個列的內容 -- 這是一個查詢 try { Statement stmt = con.createStatement(); results = stmt.executeQuery("SELECT FOODNAME FROM FOOD;"); } catch (Exception e) {System.out.println("query exception");} dumpResults("--Contents of FoodName column--"); //嘗試實際的 SQL 語句 try { Statement stmt = con.createStatement(); results = stmt.executeQuery(query); } catch (Exception e) {System.out.println("query exception");} dumpResults("--Results of Query--"); } |
該程序打印出的結果如下所示:
C:\Projects\objectJava\chapter19>java JdbcOdbc_test Connected to:jdbc:odbc:Grocery prices Driver JDBC-ODBC Bridge (ODBCJT32.DLL) --Tables-- TABLE_QUALIFIER TABLE_OWNER TABLE_NAME TABLE_TYPE REMARKS groceries null Food TABLE null groceries null FoodPrice TABLE null groceries null Stores TABLE null --Column Names-- FSKey StoreKey FoodKey Price --Contents of FoodName column-- FOODNAME Apples Oranges Hamburger Butter Milk Cola Green beans --Results of Query-- FoodName Cola |
構建更高級別的JDBC對象
從上面的例子可以明顯看出,如果可以將我們使用過的一些方法封裝在幾個更高級別對象中,那將非常有幫助,我們不僅可以封裝 try 程序塊,而且可以更簡單地訪問 ResultSet
方法。
在這一部分中,我們將構建一個新的 resultSet
對象,該對象封裝了JDBC ResultSet
對象,并以String數(shù)組的形式返回一行數(shù)據(jù)。我們發(fā)現(xiàn)您始終需要從 ResultSetMetaData
對象中獲取列的序號和名稱,因此,創(chuàng)建一個封裝元數(shù)據(jù)的新對象就非常合理。
另外,我們經常需要按名稱或整數(shù)索引提取某行的元素,如果不必總是將這些訪問語句包括 try
塊中,那將大有幫助。最后一點,如果我們需要整行的內容,則更方便的做法是將整行以String數(shù)組形式返回。在下面所示的 resultSet
對象中,我們致力于實現(xiàn)這些目標:
class resultSet { // 這個類是 JDBC ResultSet 對象的更高級抽象 ResultSet rs; ResultSetMetaData rsmd; int numCols; public resultSet(ResultSet rset) { rs = rset; try { // 同時獲取元數(shù)據(jù)和列數(shù) rsmd = rs.getMetaData(); numCols = rsmd.getColumnCount(); } catch (Exception e) {System.out.println("resultset error" +e.getMessage());} } //-- public String[] getMetaData() { // 返回包含所有列名或其他元數(shù)據(jù)的 // 一個數(shù)組 String md[] = new String[numCols]; try { for (int i=1; i<= numCols; i++) md[i-1] = rsmd.getColumnName(i); } catch (Exception e) {System.out.println("meta data error"+ e.getMessage());} return md; } //-- public boolean hasMoreElements() { try{ return rs.next(); } catch(Exception e){return false;} } //-- public String[] nextElement() { // 將行的內容復制到字符串數(shù)組中 String[] row = new String[numCols]; try { for (int i = 1; i <= numCols; i++) row[i-1] = rs.getString(i); } catch (Exception e) {System.out.println("next element error"+ e.getMessage());} return row; } //-- public String getColumnValue(String columnName) { String res = ""; try { res = rs.getString(columnName); } catch (Exception e) {System.out.println("Column value error:"+ columnName+e.getMessage());} return res; } //-- public String getColumnValue(int i) { String res = ""; try { res = rs.getString(i); } catch (Exception e) {System.out.println("Column value error:"+ columnName+e.getMessage());} return res; } //-- public void finalize() { try{rs.close();} catch (Exception e) {System.out.println(e.getMessage());} } } |
通過簡單使用 new
操作符就地創(chuàng)建一個 ResultSet
對象,我們很容易將任何 ResultSet
對象封裝在此類中:
ResultSet results = .. // 按通常的方法獲得 ResultsSet // 利用它創(chuàng)建一個更有用的對象 resultSet rs = new resultSet(results); |
并很容易在任何 JDBC 程序中使用這個對象。
構建一個Database對象
我們沿 00 鏈向上移的另一部分努力是創(chuàng)建一個 Database
對象,它將封裝下列對象的行為: Connection
、 Statement
和 DatabaseMetaData
對象, 以及我們剛剛構建的 SQL 查詢和 resultSet 。我們的 Database
對象允許我們創(chuàng)建連接、獲取表名、在數(shù)據(jù)庫中移動以及更簡單地獲得行和列的值。請注意, Execute
方法返回一個 resultSet
對象,您可以直接對它進行操作。
class Database { // 這是一個將 JDBC 數(shù)據(jù)庫的所有功能封裝在單個對象中的類 Connection con; resultSet results; ResultSetMetaData rsmd; DatabaseMetaData dma; String catalog; String types[]; public Database(String driver) { types = new String[1]; types[0] = "TABLES"; // 初始化類型 try{Class.forName(driver);} // 加載 JDBC-ODBC 橋驅動程序 catch (Exception e) {System.out.println(e.getMessage());} } //-- public void Open(String url, String cat) { catalog = cat; try {con = DriverManager.getConnection(url); dma =con.getMetaData(); // 獲取元數(shù)據(jù) } catch (Exception e) {System.out.println(e.getMessage());} } //-- public String[] getTableNames() { String[] tbnames = null; Vector tname = new Vector(); // 將表名添加到一個 Vector 中, // 因為我們不知道有多少個表 try { results = new resultSet(dma.getTables(catalog, null, "%", types)); while (results.hasMoreElements()) tname.addElement(results.getColumnValue("TABLE_NAME")); } catch (Exception e) {System.out.println(e);} // 將表名復制到一個 String 數(shù)組中 tbnames = new String[tname.size()]; for (int i=0; i< tname.size(); i++) tbnames[i] = (String)tname.elementAt(i); return tbnames; } //-- public String[] getTableMetaData() { // 返回表類型的信息 results = null; try{ results = new resultSet(dma.getTables(catalog, null, "%", types)); } catch (Exception e) {System.out.println(e.getMessage());} return results.getMetaData(); } //-- public String[] getColumnMetaData(String tablename) { // 返回一個列的數(shù)據(jù) results = null; try { results = new resultSet(dma.getColumns(catalog, null, tablename, null)); } catch (Exception e) {System.out.println(e.getMessage());} return results.getMetaData(); } //-- public String[] getColumnNames(String table) { // 返回一個列名數(shù)組 String[] tbnames = null; Vector tname = new Vector(); try { results = new resultSet(dma.getColumns(catalog, null, table, null)); while (results.hasMoreElements() ) tname.addElement(results.getColumnValue("COLUMN_NAME")); } catch (Exception e) {System.out.println(e);} tbnames = new String[tname.size()]; for (int i=0; i< tname.size(); i++) tbnames[i] = (String)tname.elementAt(i); return tbnames; } //-- public String getColumnValue(String table, String columnName) { // 返回給定列的值 String res = null; try { if (table.length()>0) results = Execute("Select " + columnName + " from " + table + " order by "+columnName); if (results.hasMoreElements()) res = results.getColumnValue(columnName); } catch (Exception e) {System.out.println("Column value error" + columnName+ e.getMessage());} return res; } //-- public String getNextValue(String columnName) { // 使用存儲的 resultSet // 返回該列的下一個值 String res = ""; try { if (results.hasMoreElements()) res = results.getColumnValue(columnName); } catch (Exception e) {System.out.println("next value error"+ columnName+ e.getMessage());} return res; } //-- public resultSet Execute(String sql) { // 對此數(shù)據(jù)庫執(zhí)行一個 SQL 查詢 results = null; try { Statement stmt = con.createStatement(); results = new resultSet(stmt.executeQuery(sql)); } catch (Exception e) {System.out.println("execute error"+ e.getMessage());} return results; } } |
一個可視化的數(shù)據(jù)庫程序
為了對我們本章學習的內容進行總結,我們編寫一個簡單的 GUI 程序,它可以顯示數(shù)據(jù)庫的表名、列名和列內容。我們還將包括一個文本區(qū)域,您可以在其中鍵入一個要對數(shù)據(jù)庫執(zhí)行的 SQL 查詢。在 Companion CD-ROM 上的 \chapter20 子目錄中,可以找到本程序(稱為 dbFrame.java )所使用的 resultSet
和 Database
類。程序的顯示界面如圖 3 所示。
圖3:用來顯示用JDBC連接的數(shù)據(jù)庫中的數(shù)據(jù)的dbFrame.java程序。 在本程序中,默認數(shù)據(jù)庫(groceries.mdb)的表名顯示在左側的欄中。當您單擊其中一個表名時,列名就會顯示在中間的欄中。最后,當您單擊中間欄中的某一行時,該行的內容就會顯示在右側的欄中。
本程序的關鍵只是接收列表選擇,然后清除并填充正確的列表框:
public void itemStateChanged(ItemEvent e) { Object obj = e.getSource(); if (obj == Tables) // 放入列名 showColumns(); if (obj == Columns) // 放入列的內容 showData(); } //-- private void loadList(List list, String[] s) { // 清除并填充指定的列表框 list.removeAll(); for (int i=0; i< s.length; i++) list.add(s[i]); } //-- private void showColumns() { // 顯示列名 String cnames[] = db.getColumnNames(Tables.getSelectedItem()); loadList(Columns, cnames); } //-- private void showData() { String colname = Columns.getSelectedItem(); String colval = db.getColumnValue(Tables.getSelectedItem(), colname); Data.setVisible(false); Data.removeAll(); Data.setVisible(true); colval = db.getNextValue(Columns.getSelectedItem()); while (colval.length()>0) { Data.add(colval); colval = db.getNextValue(Columns.getSelectedItem()); } } |
執(zhí)行查詢
顯示畫面底部的文本區(qū)域使您可鍵入所需的任何 SQL 查詢。演示程序中構建的一個查詢如下所示:
String queryText = "SELECT DISTINCTROW FoodName, StoreName, Price "+ "FROM (Food INNER JOIN FoodPrice ON "+ "Food.FoodKey = FoodPrice.FoodKey) " + "INNER JOIN Stores ON "+ "FoodPrice.StoreKey = Stores.StoreKey "+ "WHERE (((Food.FoodName)=\‘Oranges\‘)) "+ " ORDER BY FoodPrice.Price;"; |
此查詢簡單地列出每個雜貨店的桔子價格。
當您單擊 Run Query按鈕時,它將執(zhí)行此查詢,并將resultSet對象傳送給一個對話框進行顯示:
public void actionPerformed(ActionEvent e) { Object obj = e.getSource(); if (obj == Quit) System.exit(0); if (obj == Search) clickedSearch(); } //-- private void clickedSearch() { resultSet rs = db.Execute(query.getText()); String cnames[] = rs.getMetaData(); queryDialog q = new queryDialog(this, rs); q.show(); } |
查詢結果對話框
查詢對話框獲得 resultSet
對象,并將每一行放入一個 String 數(shù)組中,然后將這些 String 數(shù)組放入一個 Vector 中,這樣就可以在 paint()
子程序運行期間快速訪問這些行。
private void makeTables() { // 將每一行放入一個 String 數(shù)組中,并將 // 這些字符串數(shù)組全部放入一個 Vector 中 tables = new Vector(); String t[] = results.getMetaData(); tables.addElement( t); while (results.hasMoreElements()) tables.addElement(results.nextElement()); } |
我們通過 Graphics 的 drawString()
方法將數(shù)據(jù)繪制在一個 Panel 中。就像在 Printer 對象中一樣,我們必須自己跟蹤 x 和 y 的位置。
public void paint(Graphics g) { String s[]; int x=0; // 計算字體的高度 int y =g.getFontMetrics().getHeight(); // 估算列的高度 int deltaX = (int)1.5f* (g.getFontMetrics().stringWidth("wwwwwwwwwwwwww")); // 遍歷表矢量 for (int i=0; i< tables.size(); i++) { s = (String[])tables.elementAt(i); // 繪制字符串數(shù)組中的每一行 for (int j =0; j< s.length; j++) { String st= s[j]; g.drawString(st, x, y); x += deltaX; // 移到下一列 } x = 0; // 開始一個新行 y += g.getFontMetrics().getHeight(); // 列標簽與列數(shù)據(jù)之間的額外空間 if (i == 0) y += g.getFontMetrics().getHeight(); } } |
內建查詢的 queryDialog 如圖 4 所示。
圖4:dbFrame程序中 顯示的queryDialog,其中顯示的是默認查詢的結果。 示例文件
groceries.zip
dbFrame.zip
jdbc-odbc Bridge 小結
在本文中,我們討論了數(shù)據(jù)庫以及檢驗數(shù)據(jù)庫并對數(shù)據(jù)庫執(zhí)行查詢的方法。我們已經看到, JDBC 提供了一種與平臺和數(shù)據(jù)庫無關的、面向對象的方法來訪問這些數(shù)據(jù),我們還學習了 JDBC 的主要對象: ResultSet
、 ResultSetMetaData
和 DatabaseMetaData。
在用這些對象編寫了一個簡單的程序之后,我們設計了更高級別的 resultSet
和 Database
對象,我們用它們構建了一個簡單的可視化界面來顯示數(shù)據(jù)庫信息。
如果您熟悉數(shù)據(jù)庫的強大功能,就會認識到SQL語言可使您執(zhí)行比我們此處所述操作更強大的任務。例如,您可以創(chuàng)建新表、添加、更改或刪除表的行、列或單個表元。使用JDBC,所有這一切都變得通用和易于處理。
如果您使用的是特定平臺的數(shù)據(jù)庫驅動程序,如JDBC-ODBCBridge,則您在編寫應用程序時會受到限制,因為applet不能連接在另一臺計算機上運行的這個橋。其他客戶機-服務器數(shù)據(jù)庫,如IBM的DB2,允許您使用applet中的JDBC與其連接。
關于作者
|
| | IBM has authored this article |