簡介
有了JDBC,向各種關係數據傳送SQL語句就是一件很容易的事。換言之,有了JDBC API,就不必為訪問Sybase資料庫專門寫一個程式,為訪問Oracle資料庫又專門寫一個程式,或為訪問Informix資料庫又編寫另一個程式等等,程式設計師只需用JDBC API寫一個程式就夠了,它可向相應資料庫傳送SQL調用,將Java語言和JDBC結合起來使程式設計師只須寫一遍程式就可以讓它在任何平台上運行,這也是Java語言“編寫一次,處處運行”的優勢。
Java資料庫連線體系結構是用於Java應用程式連線資料庫的標準方法。JDBC對Java程式設計師而言是API,對實現與資料庫連線的服務提供商而言是接口模型。作為API,JDBC為程式開發提供標準的接口,並為資料庫廠商及第三方中間件廠商實現與資料庫的連線提供了標準方法。JDBC使用已有的SQL標準並支持與其它資料庫連線標準,如ODBC之間的橋接。JDBC實現了所有這些面向標準的目標並且具有簡單、嚴格類型定義且高性能實現的接口。
JDBCTM是一種用於執行SQL語句的JavaTM API,它由一組用Java程式語言編寫的類和接口組成。JDBC為工具/資料庫開發人員提供了一個標準的API,使他們能夠用純Java API 來編寫資料庫應用程式。
Java具有堅固、安全、易於使用、易於理解和可從網路上自動下載等特性,是編寫資料庫應用程式的傑出語言。所需要的只是Java應用程式與各種不同資料庫之間進行對話的方法。而JDBC正是作為此種用途的機制。
JDBC擴展了Java的功能。例如,用Java和JDBC API可以發布含有applet的網頁,而該applet使用的信息可能來自遠程資料庫企業也可以用JDBC通過Intranet將所有職員連到一個或多個內部資料庫中(即使這些職員所用的計算機有Windows、 Macintosh和UNIX等各種不同的作業系統)。隨著越來越多的程式設計師開始使用Java程式語言,對從Java中便捷地訪問資料庫的要求也在日益增加。
MIS管理員們都喜歡Java和JDBC的結合,因為它使信息傳播變得容易和經濟。企業可繼續使用它們安裝好的資料庫,並能便捷地存取信息,即使這些信息是儲存在不同資料庫管理系統上。新程式的開發期很短。安裝和版本控制將大為簡化。程式設計師可只編寫一遍應用程式或只更新一次,然後將它放到伺服器上,隨後任何人就都可得到最新版本的應用程式。對於商務上的銷售信息服務,Java和JDBC可為外部客戶提供獲取信息更新的更好方法。
用途
簡單地說,JDBC可做三件事:與資料庫建立連線、傳送SQL語句並處理結果。下列代碼段給出了以上三步的基本示例:
Connection con = DriverManager.getConnection("jdbc:odbc:wombat","login","password");
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table1");
while (rs.next()) {
int x = rs.getInt("a");
String s = rs.getString("b");
float f = rs.getFloat("c"); }
上述代碼對基於JDBC的資料庫訪問做了經典的總結,當然,在本小節的後續部分會對它做詳盡的分析講解。
API
JDBC是個“低級”接口,也就是說,它用於直接調用SQL命令。在這方面它的功能極佳,並比其它的資料庫連線API易於使用,但它同時也被設計為一種基礎接口,在它之上可以建立高級接口和工具。高級接口是“對用戶友好的”接口,它使用的是一種更易理解和更為方便的API,這種API在幕後被轉換為諸如JDBC這樣的低級接口。
在關係資料庫的“對象/關係”映射中,表中的每行對應於類的一個實例,而每列的值對應於該實例的一個屬性。於是,程式設計師可直接對Java對象進行操作;存取數據所需的SQL調用將在“掩蓋下”自動生成。此外還可提供更複雜的映射,例如將多個表中的行結合進一個Java類中。
隨著人們對JDBC的興趣日益增漲,越來越多的開發人員一直在使用基於JDBC的工具,以使程式的編寫更加容易。程式設計師也一直在編寫力圖使最終用戶對資料庫的訪問變得更為簡單的應用程式。例如應用程式可提供一個選擇資料庫任務的選單。任務被選定後,應用程式將給出提示及空白供填寫執行選定任務所需的信息。所需信息輸入應用程式將自動調用所需的SQL命令。在這樣一種程式的協助下,即使用戶根本不懂SQL的語法,也可以執行資料庫任務。
比較
目前,Microsoft的ODBCAPI可能是使用最廣的、用於訪問關係資料庫的編程接口。它能在幾乎所有平台上連線幾乎所有的資料庫。為什麼Java不使用ODBC?對這個問題的回答是:Java可以使用ODBC,但最好是在JDBC的幫助下以JDBC-ODBC橋的形式使用,這一點我們稍後再說。現在的問題已變成:“為什麼需要JDBC”?答案是顯然的:ODBC不適合直接在Java中使用,因為它使用C語言接口。從Java調用本地C代碼在安全性、實現、堅固性和程式的自動移植性方面都有許多缺點。從ODBCCAPI到JavaAPI的字面翻譯是不可取的。例如,Java沒有指針,而ODBC卻對指針用得很廣泛(包括很容易出錯的指針“void*”)。您可以將JDBC想像成被轉換為面向對象接口的ODBC,而面向對象的接口對Java程式設計師來說較易於接受。
ODBC很難學。它把簡單和高級功能混在一起,而且即使對於簡單的查詢,其選項也極為複雜。相反,JDBC儘量保證簡單功能的簡便性,而同時在必要時允許使用高級功能。啟用“純Java”機制需要象JDBC這樣的JavaAPI。如果使用ODBC,就必須手動地將ODBC驅動程式管理器和驅動程式安裝在每台客戶機上。如果完全用Java編寫JDBC驅動程式則JDBC代碼在所有Java平台上(從網路計算機到大型機)都可以自動安裝、移植並保證安全性。
總之,JDBCAPI對於基本的SQL抽象和概念是一種自然的Java接口。它建立在ODBC上而不是從零開始。因此,熟悉ODBC的程式設計師將發現JDBC很容易使用。JDBC保留了ODBC的基本設計特徵;事實上,兩種接口都基於X/OpenSQLCLI(調用級接口)。它們之間最大的區別在於:JDBC以Java風格與優點為基礎並進行最佳化,因此更加易於使用。
目前,Microsoft又引進了ODBC之外的新API:RDO、ADO和OLEDB。這些設計在許多方面與JDBC是相同的,即它們都是面向對象的資料庫接口且基於可在ODBC上實現的類。但在這些接口中,我們未看見有特別的功能使我們要轉而選擇它們來替代ODBC,尤其是在ODBC驅動程式已建立起較為完善的市場的情況下。它們最多也就是在ODBC上加了一種裝飾而已。
支持
JDBCAPI既支持資料庫訪問的兩層模型(C/S),同時也支持三層模型(B/S)。在兩層模型中,Javaapplet或應用程式將直接與資料庫進行對話。這將需要一個JDBC驅動程式來與所訪問的特定資料庫管理系統進行通訊。用戶的SQL語句被送往資料庫中,而其結果將被送回給用戶。資料庫可以位於另一台計算機上,用戶通過網路連線到上面。這就叫做客戶機/伺服器配置,其中用戶的計算機為客戶機,提供資料庫的計算機為伺服器。網路可以是Intranet(它可將公司職員連線起來),也可以是Internet。
在三層模型中,命令先是被傳送到服務的“中間層”,然後由它將SQL語句傳送給資料庫。資料庫對SQL語句進行處理並將結果送回到中間層,中間層再將結果送回給用戶。MIS主管們都發現三層模型很吸引人,因為可用中間層來控制對公司數據的訪問和可作的的更新的種類。中間層的另一個好處是,用戶可以利用易於使用的高級API,而中間層將把它轉換為相應的低級調用。最後,許多情況下三層結構可提供一些性能上的好處。
到目前為止,中間層通常都用C或C++這類語言來編寫,這些語言執行速度較快。然而,隨著最最佳化編譯器(它把Java位元組代碼轉換為高效的特定於機器的代碼)的引入,用Java來實現中間層將變得越來越實際。這將是一個很大的進步,它使人們可以充分利用Java的諸多優點(如堅固、多執行緒和安全等特徵)。JDBC對於從Java的中間層來訪問資料庫非常重要。
一致性
結構化查詢語言(SQL)是訪問關係資料庫的標準語言。困難之處在於:雖然大多數的DBMS(資料庫管理系統)對其基本功能都使用了標準形式的SQL,但它們卻不符合最近為更高級的功能定義的標準SQL語法或語義。例如,並非所有的資料庫都支持儲存程式或外部連線,那些支持這一功能的資料庫又相互不一致。人們希望SQL中真正標準的那部份能夠進行擴展以包括越來越多的功能。但同時JDBCAPI又必須支持現有的SQL。
JDBCAPI解決這個問題的一種方法是允許將任何查詢字元串一直傳到所涉及的DBMS驅動程式上。這意味著應用程式可以使用任意多的SQL功能,但它必須冒這樣的風險:有可能在某些DBMS上出錯。事實上,應用程式查詢甚至不一定要是SQL,或者說它可以是個為特定的DBMS設計的SQL的專用派生物(例如,文檔或圖象查詢)。
JDBC處理SQL一致性問題的第二種方法是提供ODBC風格的轉義子句,這將在後續部分中討論。轉義語法為幾個常見的SQL分歧提供了一種標準的JDBC語法。例如,對日期文字和已儲存過程的調用都有轉義語法。
對於複雜的應用程式,JDBC用第三種方法來處理SQL的一致性問題它利用DatabaseMetaData接口來提供關於DBMS的描述性信息,從而使應用程式能適應每個DBMS的要求和功能。
由於JDBCAPI將用作開發高級資料庫訪問工具和API的基礎API,因此它還必須注意其所有上層建築的一致性。“符合JDBC標準TM”代表用戶可依賴的JDBC功能的標準級別。要使用這一說明,驅動程式至少必須支持ANSISQL-2EntryLevel(ANSISQL-2代表美國國家標準局1992年所採用的標準。EntryLevel代表SQL功能的特定清單)。驅動程式開發人員可用JDBCAPI所帶的測試工具包來確定他們的驅動程式是否符合這些標準。
“符合JDBC標準TM”表示提供者的JDBC實現已經通過了JavaSoft提供的一致性測試。這些一致性測試將檢查JDBCAPI中定義的所有類和方法是否都存在,並儘可能地檢查程式是否具有SQLEntryLevel功能。當然,這些測試並不完全,而且JavaSoft目前也無意對各提供者的實現進行標級。但這種一致性定義的確可對JDBC實現提供一定的可信度。隨著越來越多的資料庫提供者、連線提供者、Internet提供者和應用程式編程員對JDBCAPI的接受,JDBC也正迅速成為Java資料庫訪問的標準。
聯接DBMS
裝載驅動程式
你需要做的第一事情是你與想要使用的DBMS建立一個連線。這包含2個步驟:裝載驅動程式並建立連線。
裝載驅動程式只需要非常簡單的一行代碼。例如,你想要使用JDBC-ODBC橋驅動程式,可以用下列代碼裝載它:
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
你的驅動程式文檔將告訴你應該使用的類名。例如,如果類名是jdbc.DriverXYZ,你將用代碼以下的代碼裝載驅動程式:
Class.forName("jdbc.DriverXYZ");
你不需要創建一個驅動程式類的實例並且用DriverManager登記它,因為調用Class.forName將自動載入驅動程式類。如果你曾自己創建實例,你將創建一個不必要的副本,但它不會帶來什麼壞處。
載入Driver類後,它們即可用來與資料庫建立連線。
建立連線
第二步就是用適當的驅動程式類與DBMS建立一個連線。下列代碼是一般的做法:
Connectioncon=DriverManager.getConnection(url,"myLogin","myPassword");
這個步驟也非常簡單,最難的是怎么提供url。如果你正在使用JDBC-ODBC橋,JDBCURL將以jdbc:odbc開始:餘下URL通常是你的數據源名字或資料庫系統。因此,假設你正在使用ODBC存取一個叫"Fred"的ODBC數據源,你的JDBCURL是jdbc:odbc:Fred。把"myLogin"及"myPassword"替換為你登入DBMS的用戶名及口令。如果你登入資料庫系統的用戶名為"Fernanda"口令為"J8",只需下面的2行代碼就可以建立一個連線:
Stringurl="jdbc:odbc:Fred";
Connectioncon=DriverManager.getConnection(url,"Fernanda","J8");
如果你使用的是第三方開發了的JDBC驅動程式,文檔將告訴你該使用什麼subprotocol,就是在JDBCURL中放在jdbc後面的部分。例如,如果驅動程式開發者註冊了acme作為subprotocol,JDBCURL的第一和第二部分將是jdbc:acme。驅動程式文檔也會告訴你餘下JDBCURL的格式。JDBCURL最後一部分提供了定位資料庫的信息。
如果你裝載的驅動程式識別了提供給DriverManager.getConnection的JDBCURL,那個驅動程式將根據JDBCURL建立一個到指定DBMS的連線。正如名稱所示,DriverManager類在幕後為你管理建立連線的所有細節。除非你是正在寫驅動程式,你可能無需使用此類的其它任何方法,一般程式設計師需要在此類中直接使用的唯一方法是DriverManager.getConnection。
DriverManager.getConnection方法返回一個打開的連線,你可以使用此連線創建JDBCstatements並傳送SQL語句到資料庫。在前面的例子裡,con對象是一個打開的連線,並且我們要在以後的例子裡使用它。
設定表
創建表
首先,我們在我們的示例資料庫創建其中一張表 COFFEES,包含在咖啡店所賣咖啡的必要的信息,包括咖啡名字,他們的價格,本星期賣了多少磅及迄今為止賣的數目。關於 COFFEES表我們以後會詳細描述,如下:
COF_NAME SUP_ID PRICE SALES TOTAL
Colombian 101 7.99 0 0
French_Roast 49 8.99 0 0
Espresso 150 9.99 0 0
Colombian_Decaf 101 8.99 0 0
French_Roast_Decaf 49 9.99 0 0
存儲咖啡名的列是COF_NAME,它的SQL數據類型是VARCHAR,最大的長度為32個字元。因為我們所賣的每種類型咖啡都使用不同的名字,名字可用於作為唯一識別咖啡的標識,因此可用於作主鍵。第二個列叫SUP_ID,用於保存咖啡供應商標識;其SQL數據類型為INTEGER。第3列叫PRICE,因為它需要保存帶小數的十進制數,因此它的SQL類型為FLOAT。(注意,通常錢的SQL類型為DECIMAL或NUMERIC,但在不同DBMSs間存在差異,為了避免於老版本的JDBC的不兼容性在本教程我們採用更標準的FLOAT類型)SALES列的SQL 類型為INTEGER,其值為本星期所賣咖啡的磅數。最後一列,TOTAL的SQL類型為INTEGER,保存了迄今為止所賣咖啡的總磅數。
資料庫里的第二個表SUPPLIERS,保存了每個供應商的信息:
SUP_ID SUP_NAME STREET CITY STATE ZIP
101 Acme, Inc. 99 Market Street Groundsville CA 95199
49 Superior Coffee 1 Party Place Mendocino CA 95460
150 The High Ground 100 Coffee Lane Meadows CA 93966
COFFEES跟SUPPLIERS都包含列SUP_ID,它意味著可以用SELECT語句從這兩張表中取得有關信息。列SUP_ID是SUPPLIERS表的主鍵,用於唯一識別每個咖啡供應商。在COFFEES表中,SUP_ID列被稱外鍵。注意每個SUP_ID值在SUPPLIERS表里只出現一次;這對主鍵是必須的。在COFFEES表里,它作為外鍵,顯然它可以有重複的SUP_ID值,因為同一供應商可以提供很多種的咖啡。在本節的最後,你將看見如何在SELECT語句中使用主鍵及外鍵的一個例子。
下面的SQL語句用於䲁列(包括列名及其SQL類型)跟下一個之間用逗號分隔。VARCHAR類型創建定義了最大長度,因此它需要有一個參數來表示最大長度。參數必須在類型後面的括弧內。SQL語句如下,列COF_NAME的長度 被限定為不得超過32個字元:
CREATE TABLE COFFEES
(COF_NAME VARCHAR(32),
SUP_ID INTEGER,
PRICE FLOAT,
SALES INTEGER,
TOTAL INTEGER)
這些代碼不帶DBMS語句結束符, 因為每個DBMS都可能不同。例如,Oracle使用一個分號(;) 作為語句的結束,而Sybase使用go。你所使用的驅動程式會自動提供合適的語句結束符,因此你無須把它包括在你的JDBC代碼中。
另外,我們應該指出的的是SQL語句的格式。在CREATE TABLE語句中,關鍵字採用大寫字元,並且每個項目都另起一行。SQL並沒有此要求;僅僅是為了更容易閱讀。SQL標準是不區分關鍵字的大小寫的,因此,如下例中的SELECT語句可以有多種寫法。因此下面兩個不同寫法的語句對SQL來說是一樣的。
SELECT First_Name, Last_Name
FROM Employees
WHERE Last_Name LIKE "Washington"
select First_Name, Last_Name from Employees where
Last_Name like "Washington"
然而,引號里的內容是區分大小寫的:在名字“Washington”里“W”必須被大寫,並且餘下的字元必須是小寫的。
對於標識,不同的DBMS有不同的要求,例如,某些DBMSs要求那些列名及表名必須跟創建時的一樣,有些則沒有此要求。為安全起見,我們全部使用大寫標識如COFFEES、SUPPLIERS,因為我們是那樣定義他們的。
到止我們寫了創建COFFEES表的SQL語句。現在我們在它外面加上引號(使它成為字元串),並且字元串賦值給變數createTableCoffees,在以後的JDBC代碼中我們可以使用此變數。正如看到的,DBMS並不在意分行,但對Java語言來,String對象分行是通不過編譯的。因而,我們可以用加號 (+) 把每一行的串連線。
String createTableCoffees = "CREATE TABLE COFFEES " +"(COF_NAME VARCHAR(32), SUP_ID INTEGER, PRICE FLOAT, " +"SALES INTEGER, TOTAL INTEGER)";
我們在CREATE TABLE語句中使用的數據類型是通用的SQL類型(也稱JDBC類型)它們在類java.sql.Types中定義。DBMSs通常使用這些標準的類型,因此,當你要嘗試一些JDBC應用程式時,你可以直接使用CreateCoffees.java應用程式,它使用了CREATE TABLE語句。如果你的DBMS使用了它的自己的本地的類型名字,我們為你供應其它的應用程式,我們將在後面詳細解釋。
在運用任何應用程式前,當然,我們將讓你了解JDBC的基礎。
創建JDBC Statements對象
Statement對象用於把SQL語句傳送到DBMS。你只須簡單地創建一個Statement對象並且然後執行它,使用適當的方法執行你傳送的SQL語句。對SELECT語句來說,可以使用executeQuery。要創建或修改表的語句,使用的方法是executeUpdate。
需要一個活躍的連線的來創建Statement對象的實例。在下面的例子中,我們使用我們的Connection對象con創建Statement對象stmt:
Statement stmt = con.createStatement();
到此stmt已經存在了,但它還沒有把SQL語句傳遞到DBMS。我們需要提供SQL語句作為參數提供給我們使用的Statement的方法。例如,在下面的代碼段里,我們使用上面例子中的SQL語句作為executeUpdate的參數:
stmt.executeUpdate("CREATE TABLE COFFEES " +"(COF_NAME VARCHAR(32), SUP_ID INTEGER, PRICE FLOAT, " +"SALES INTEGER, TOTAL INTEGER)");
因為我們已經把SQL語句賦給了createTableCoffees變數,我們可以如下方式書寫代碼:stmt.executeUpdate(createTableCoffees);
執行語句
我們使用executeUpdate方法是因為在createTableCoffees中的SQL語句是DDL(數據定義語言)語句。創建表,改變表,刪除表都是DDL語句的例子,要用executeUpdate方法來執行。你也可以從它的名字里看出,方法executeUpdate也被用於執行更新表SQL語句。實際上,相對於創建表來說,executeUpdate用於更新表的時間更多,因為表只需要創建一次,但經常被更新。
被使用最多的執行SQL語句的方法是executeQuery。這個方法被用來執行SELECT語句,它幾乎是使用最多的SQL語句。馬上你將看到如何使用這個方法。
在表中輸入數據
我們已經顯示了如何通過指定列名、數據類型來創建表COFFEES,但是這僅僅建立表的結構。表還沒有任何數據。我們將次輸入一行數據到表中,提供每列的信息,注意插入的數據顯示順序跟表創建時候是一樣的,既預設順序。
下列代碼插入一個行數據,COF_NAME的值為Colombian,SUP_ID 為 101,PRICE 為 7.99,SALES 0,TOTAL 0。就象創建COFFEES表一樣,我們創建一Statement對象,並執行executeUpdate方法。
因為SQL語句一行顯示不下,因此我們把它分為兩行,並用加號(+) 相連。特別要注意的是,在COFFEES和VALUES之間要有空格。這個空格必須在引號之內並且要在COFFEES跟VALUES之間;沒有這個空格,SQL語句將被錯誤地被讀作為"INSERT INTO COFFEESVALUES ...",並且DBMS將尋找表COFFEESVALUES。還要注意的是在coffee name上我們使用了單引號。
Statement stmt = con.createStatement();
stmt.executeUpdate(
"INSERT INTO COFFEES " +
"VALUES ('Colombian', 101, 7.99, 0, 0)");
下面的代碼把第二行插入到表COFFEES 中。我們可以在使用Statement對象而無須為每次執行創建一個新的。
stmt.executeUpdate("INSERT INTO COFFEES " +
"VALUES ('French_Roast', 49, 8.99, 0, 0)");
剩下行的數據如下:
stmt.executeUpdate("INSERT INTO COFFEES " +
"VALUES ('Espresso', 150, 9.99, 0, 0)");
stmt.executeUpdate("INSERT INTO COFFEES " +
"VALUES ('Colombian_Decaf', 101, 8.99, 0, 0)");
stmt.executeUpdate("INSERT INTO COFFEES " +
"VALUES ('French_Roast_Decaf', 49, 9.99, 0, 0)");
從表中取得數據
既然表COFFEES中已經有數據了,我們就可以寫一個SELECT語句來取得這些值。下面的SQL語句中星號(*)表示選擇所有的列。因為沒有用WHERE子句來限制所選的行,因此下面的SQL語句選擇的是整個表。
SELECT * FROM COFFEES
結果是整個表的數據,如下:
COF_NAME SUP_ID PRICE SALES TOTAL
--------------- ------ ----- ----- -----
Colombian 101 7.99 0 0
French_Roast 49 8.99 0 0
Espresso 150 9.99 0 0
Colombian_Decaf 101 8.99 0 0
French_Roast_Decaf 49 9.99 0 0
如果你直接在資料庫系統里輸入SQL查詢語句,你將在你的終端上看到如上的結果。當我們通過一個Java應用程式存取一個資料庫時,正如我們馬上要做的一樣,我們需要檢索結果以便我們能使用他們。你將在下一節看到如何實現。
這是SELECT語句的另一個例子,這將得到咖啡及其各自每磅單價的列表。
SELECT COF_NAME, PRICE FROM COFFEES
查詢的結果集將具有如下形式:
COF_NAME PRICE
-------- ---------- -----
Colombian 7.99
French_Roast 8.99
Espresso 9.99
Colombian_Decaf 8.99
French_Roast_Decaf 9.99
上面SELECT語句取得了所有咖啡的名字及價格。而下面的SELECT語句限制那些每磅價格低於$9.00的咖啡才被選擇。
SELECT COF_NAME, PRICE
FROM COFFEES
WHERE PRICE < 9.00
結果集將具有如下形式:
COF_NAME PRICE
-------- ------- -----
Colombian 7.99
French_Roast 8.99
Colombian Decaf 8.99
驅動管理
綜述
DriverManager類是JDBC的管理層,作用於用戶和驅動程式之間。它跟蹤可用的驅動程式,並在資料庫和相應驅動程式之間建立連線。另外,DriverManager類也處理諸如驅動程式登錄時間限制及登錄和跟蹤訊息的顯示等事務。對於簡單的應用程式,一般程式設計師需要在此類中直接使用的唯一方法是DriverManager.getConnection。正如名稱所示,該方法將建立與資料庫的連線。JDBC允許用戶調用DriverManager的方法getDriver、getDrivers和registerDriver及Driver的方法connect。但多數情況下,讓DriverManager類管理建立連線的細節為上策。
跟蹤驅動程式
DriverManager類包含一列Driver類,它們已通過調用方法DriverManager.registerDriver對自己進行了註冊。所有Driver類都必須包含有一個靜態部分。它創建該類的實例,然後在載入該實例時DriverManager類進行註冊。這樣,用戶正常情況下將不會直接調用DriverManager.registerDriver;而是在載入驅動程式時由驅動程式自動調用。載入Driver類,然後自動在DriverManager中註冊的方式有兩種:
(1)調用方法Class.forName
這將顯式地載入驅動程式類。由於這與外部設定無關,因此推薦使用這種載入驅動程式的方法。以下代碼載入類acme.db.Driver:Class.forName("acme.db.Driver")。
如果將acme.db.Driver編寫為載入時創建實例,並調用以該實例為參數的DriverManager.registerDriver(本該如此),則它在DriverManager的驅動程式列表中,並可用於創建連線。
(2)將驅動程式添加到Java.lang.System的屬性jdbc.drivers中
這是一個由DriverManager類載入的驅動程式類名的列表,由冒號分隔:初始化DriverManager類時,它搜尋系統屬性jdbc.drivers,如果用戶已輸入了一個或多個驅動程式,則DriverManager類將試圖載入它們。以下代碼說明程式設計師如何在~/.hotJava/properties中輸入三個驅動程式類(啟動時,HotJava將ivers=foo.bah.Driver:wombat.sql.Driver:bad.test.ourDriver;
對DriverManager方法的第一次調用將自動載入這些驅動程式類。注意:載入驅動程式的第二種方法需要持久的預設環境。如果對這一點不能保證,則調用方法Class.forName顯式地載入每個驅動程式就顯得更為安全。這也是引入特定驅動程式的方法,因為一旦DriverManager類被初始化,它將不再檢查jdbc.drivers屬性列表。
在以上兩種情況中,新載入的Driver類都要通過調用DriverManager.registerDriver類進行自我註冊。如上所述,載入類時將自動執行這一過程。
由於安全方面的原因,JDBC管理層將跟蹤哪個類載入器提供哪個驅動程式。這樣,當DriverManager類打開連線時,它僅使用本地檔案系統或與發出連線請求的代碼相同的類載入器提供的驅動程式。
建立連線
載入Driver類並在DriverManager類中註冊後,它們即可用來與資料庫建立連線。當調用DriverManager.getConnection方法發出連線請求時,DriverManager將檢查每個驅動程式,查看它是否可以建立連線。
有時可能有多個JDBC驅動程式可以與給定的URL連線。例如,與給定遠程資料庫連線時,可以使用JDBC-ODBC橋驅動程式、JDBC到通用網路協定驅動程式或資料庫廠商提供的驅動程式。在這種情況下測試驅動程式的順序至關重要,因為DriverManager將使用它所找到的第一個可以成功連線到給定URL的驅動程式。
首先DriverManager試圖按註冊的順序使用每個驅動程式(jdbc.drivers中列出的驅動程式總是先註冊)。它將跳過代碼不可信任的驅動程式,除非載入它們的源與試圖打開連線的代碼的源相同。它通過輪流在每個驅動程式上調用方法Driver.connect,並向它們傳遞用戶開始傳遞給方法DriverManager.getConnection的URL來對驅動程式進行測試,然後連線第一個認出該URL的驅動程式。這種方法初看起來效率不高,但由於不可能同時載入數十個驅動程式,因此每次連線實際只需幾個過程調用和字元串比較。
以下代碼是通常情況下用驅動程式(例如JDBC-ODBC橋驅動程式)建立連線所需所有步驟的示例:
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");//載入驅動程式 String url = "jdbc:odbc:fred"; DriverManager.getConnection(url,"userID","passwd");
傳送語句
綜述
Statement對象用於將SQL語句傳送到資料庫中。實際上有三種Statement對象,它們都作為在給定連線上執行SQL語句的包容器:Statement、PreparedStatement(它從Statement繼承而來)和CallableStatement(它從PreparedStatement繼承而來)。它們都專用於傳送特定類型的SQL語句:Statement對象用於執行不帶參數的簡單SQL語句;PreparedStatement對象用於執行帶或不帶IN參數的預編譯SQL語句;CallableStatement對象用於執行對資料庫已存儲過程的調用。Statement接口提供了執行語句和獲取結果的基本方法;PreparedStatement接口添加了處理IN參數的方法;而CallableStatement添加了處理OUT參數的方法。
創建對象
建立了到特定資料庫的連線之後,就可用該連線傳送SQL語句。Statement對象用Connection的方法createStatement創建,如下列代碼段中所示:
Connection con = DriverManager.getConnection(url,"sunny",""); Statement stmt = con.createStatement();
為了執行Statement對象,被傳送到資料庫的SQL語句將被作為參數提供給Statement的方法:
ResultSet rs = stmt.executeQuery("SELECT a,b,c FROM Table2");
使用執行語句
Statement接口提供了三種執行SQL語句的方法:executeQuery、executeUpdate和execute。使用哪一個方法由SQL語句所產生的內容決定。
方法executeQuery用於產生單個結果集的語句,例如SELECT語句。方法executeUpdate用於執行INSERT、UPDATE或DELETE語句以及SQL DDL(數據定義語言)語句,例如CREATE TABLE和DROP TABLE。INSERT、UPDATE或DELETE語句的效果是修改表中零行或多行中的一列或多列。executeUpdate的返回值是一個整數,指示受影響的行數(即更新計數)。對於CREATE TABLE或DROP TABLE等不操作行的語句,executeUpdate的返回值總為零。
執行語句的所有方法都將關閉所調用的Statement對象的當前打開結果集(如果存在)。這意味著在重新執行Statement對象之前,需要完成對當前ResultSet對象的處理。應注意,繼承了Statement接口中所有方法的PreparedStatement接口都有自己的executeQuery、executeUpdate和execute方法。Statement對象本身不包含SQL語句,因而必須給Statement.execute方法提供SQL語句作為參數。PreparedStatement對象並不需要SQL語句作為參數提供給這些方法,因為它們已經包含預編譯SQL語句。
CallableStatement對象繼承這些方法的PreparedStatement形式。對於這些方法的PreparedStatement或CallableStatement版本,使用查詢參數將拋出SQLException。
語句完成
當連線處於自動提交模式時,其中所執行的語句在完成時將自動提交或還原。語句在已執行且所有結果返回時,即認為已完成。對於返回一個結果集的executeQuery方法,在檢索完ResultSet對象的所有行時該語句完成。對於方法executeUpdate,當它執行時語句即完成。但在少數調用方法execute的情況中,在檢索所有結果集或它生成的更新計數之後語句才完成。
有些DBMS將已存儲過程中的每條語句視為獨立的語句;而另外一些則將整個過程視為一個複合語句。在啟用自動提交時,這種差別就變得非常重要,因為它影響什麼時候調用commit方法。在前一種情況中,每條語句單獨提交;在後一種情況中,所有語句同時提交。
關閉對象
Statement對象將由Java垃圾收集程式自動關閉。而作為一種好的編程風格,應在不需要Statement對象時顯式地關閉它們。這將立即釋放DBMS資源,有助於避免潛在的記憶體問題。
使用execute
execute方法應該僅在語句能返回多個ResultSet對象、多個更新計數或ResultSet對象與更新計數的組合時使用。當執行某個已存儲過程或動態執行未知SQL字元串(即應用程式程式設計師在編譯時未知)時,有可能出現多個結果的情況,儘管這種情況很少見。例如,用戶可能執行一個已存儲過程,並且該已存儲過程可執行更新,然後執行選擇,再進行更新,再進行選擇,等等。通常使用已存儲過程的人應知道它所返回的內容。
因為方法execute處理非常規情況,所以獲取其結果需要一些特殊處理並不足為怪。例如,假定已知某個過程返回兩個結果集,則在使用方法execute執行該過程後,必須調用方法getResultSet獲得第一個結果集,然後調用適當的getXXX方法獲取其中的值。要獲得第二個結果集,需要先調用getMoreResults方法,然後再調用getResultSet方法。如果已知某個過程返回兩個更新計數,則首先調用方法getUpdateCount,然後調用getMoreResults,並再次調用getUpdateCount。
對於不知道返回內容,則情況更為複雜。如果結果是ResultSet對象,則方法execute返回true;如果結果是Javaint,則返回false。如果返回int,則意味著結果是更新計數或執行的語句是DL命令。在調用方法execute之後要做的第一件事情是調用getResultSet或getUpdateCount。調用方法getResultSet可以獲得兩個或多個ResultSet對象中第一個對象;或調用方法getUpdateCount可以獲得兩>
當SQL語句的結果不是結果集時,則方法getResultSet將返回null。這可能意味著結果是一個更新計數或沒有其它結果。在這種情況下,判斷null真正含義的唯一方法是調用方法getUpdateCount,它將返回一個整數。這個整數為調用語句所影響的行數;如果為-1則表示結果是結果集或沒有結果。如果方法getResultSet已返回null(表示結果不是ResultSet對象),則返回值-1表示沒有其它結果。也就是說,當下列條件為真時表示沒有結果(或沒有其它結果):
((stmt.getResultSet()==null)&&(stmt.getUpdateCount()==-1))
如果已經調用方法getResultSet並處理了它返回的ResultSet對象,則有必要調用方法getMoreResults以確定是否有其它結果集或更新計數。如果getMoreResults返回true,則需要再次調用getResultSet來檢索下一個結果集。如上所述,如果getResultSet返回null,則需要調用getUpdateCount來檢查null是表示結果為更新計數還是表示沒有其它結果。
當getMoreResults返回false時,它表示該SQL語句返回一個更新計數或沒有其它結果。因此需要調用方法getUpdateCount來檢查它是哪一種情況。在這種情況下,當下列條件為真時表示沒有其它結果:
((stmt.getMoreResults()==false)&&(stmt.getUpdateCount()==-1))
訪問資料庫
通用Bean
本實例中對資料庫連線和執行SQL語句等通用資料庫操作進行了封裝,通過實現DBConnBean和DBQueryBean兩個JavaBean來完成上述功能。其中DBConnBean負責Java應用程式和資料庫的連線;DBQueryBean提供了一組執行標準SQL的功能,可以實現標準SQL完成的所有功能。
資料庫表結構
本實例中主要出現了三個資料庫表,表名和欄位分別如下所示:
計畫採購表:jhcg_table
欄位名稱 中文名稱 類型 長度 Goods_no 物品編號 vchar 10 Goods_name 物品名稱 Vchar 50 Amount 採購數量 Int Price 採購單價 float Gold 幣種 Vchar 15 Units 單位 Vchar 10 Date 時間 Date Remark 備註 vchar 100
庫存統計表:kctj_table
欄位名稱 中文名稱 類型 長度 Goods_no 物品編號 Vchar 10 Goods_name 物品名稱 Vchar 50 amount 庫存數量 Int Date 時間 Date
remark 備註 Vchar 100
實際採購表:sjcg_table
欄位名稱 中文名稱 類型 長度 Goods_no 物品編號 Vchar 10 Goods_name 物品名稱 Vchar 50 Amount 採購數量 Int Price Price 採購單價 Float Gold 幣種 Vchar 15 Units 採購單位 Vchar 10 Date 時間 Date Remark 備註 vchar 100
其中業務邏輯非常簡單,即根據計畫採購表和庫存統計表生成實際採購表。同時,對各表完成資料庫的增、刪、改、查等通用操作。
① 插入操作
完成對資料庫表的記錄插入功能,
② 查詢操作
該查詢主頁面主要提供對三個資料庫表的條件查詢功能,
③ 生成實際採購表
生成資料庫表是一個隱式操作,程式根據計畫採購表和庫存統計表的相應欄位生成實際採購表,不需要用戶的任何輸入。
上述的開發工具綜合套用介紹了基於Java開發電子商務套用系統的全過程,包括套用開發平台搭建、業務流程分析、JavaBean封裝和JSP開發等內容,其中JSP開發中涉及到了通用SQL(查詢和插入資料庫表)和游標操作(生成實際採購表),基本可以完成任何網路資料庫套用的需求。本實例基本上可以將前面介紹的基於Java的電子商務開發技術串接起來,指導讀者進行電子商務套用開發。
分頁
分頁顯示是Web資料庫套用中經常需要遇到的問題,當用戶的資料庫查詢結果遠遠超過了計算機螢幕的顯示能力的時候,我們該如何合理的將數據呈現給用戶呢?答案就是資料庫分頁顯示,可以完美的解決上述問題。下面是一個資料庫分頁操作的通用實例,對任何資料庫平台上的分頁功能都有很好的借鑑意義。
選擇
JavaSoft框架
JavaSoft提供三種JDBC產品組件,它們是Java開發工具包(JDK)的組成部份:JDBC驅動程式管理器、JDBC驅動程式測試工具包和JDBC-ODBC橋。
JDBC驅動程式管理器是JDBC體系結構的支柱。它實際上很小,也很簡單;其主要作用是把Java應用程式連線到正確的JDBC驅動程式上,然後即退出。
JDBC驅動程式測試工具包為使JDBC驅動程式運行您的程式提供一定的可信度。只有通過JDBC驅動程式測試的驅動程式才被認為是符合JDBC標準TM的。
JDBC-ODBC橋使ODBC驅動程式可被用作JDBC驅動程式。它的實現為JDBC的快速發展提供了一條途徑,其長遠目標提供一種訪問某些不常見的DBMS(如果對這些不常見的DBMS未實現JDBC)的方法。
JDBC驅動程式的類型
目前比較常見的JDBC驅動程式可分為以下四個種類:
(1)JDBC-ODBC橋加ODBC驅動程式
JavaSoft橋產品利用ODBC驅動程式提供JDBC訪問。注意,必須將ODBC二進制代碼(許多情況下還包括資料庫客戶機代碼)載入到使用該驅動程式的每個客戶機上。因此,這種類型的驅動程式最適合於企業網(這種網路上客戶機的安裝不是主要問題),或者是用Java編寫的三層結構的應用程式伺服器代碼。
(2)本地API
這種類型的驅動程式把客戶機API上的JDBC調用轉換為Oracle、Sybase、Informix、DB2或其它DBMS的調用。注意,象橋驅動程式一樣,這種類型的驅動程式要求將某些二進制代碼載入到每台客戶機上。
(3)JDBC網路純Java驅動程式
這種驅動程式將JDBC轉換為與DBMS無關的網路協定,之後這種協定又被某個伺服器轉換為一種DBMS協定。這種網路伺服器中間件能夠將它的純Java客戶機連線到多種不同的資料庫上。所用的具體協定取決於提供者。通常,這是最為靈活的JDBC驅動程式。有可能所有這種解決方案的提供者都提供適合於Intranet用的產品。為了使這些產品也支持Internet訪問,它們必須處理Web所提出的安全性、通過防火牆的訪問等方面的額外要求。幾家提供者正將JDBC驅動程式加到他們現有的資料庫中間件產品中。
(4)本地協定純Java驅動程式
這種類型的驅動程式將JDBC調用直接轉換為DBMS所使用的網路協定。這將允許從客戶機機器上直接調用DBMS伺服器,是Intranet訪問的一個很實用的解決方法。由於許多這樣的協定都是專用的,因此資料庫提供者自己將是主要來源,有幾家提供者已在著手做這件事了。
JDBC驅動程式的獲取
目前已有幾十個(1)類的驅動程式,即可與Javasoft橋聯合使用的ODBC驅動程式的驅動程式。有大約十多個屬於種類(2)的驅動程式是以DBMS的本地API為基礎編寫的。只有幾個屬於種類(3)的驅動程式,其首批提供者是SCO、OpenHorizon、Visigenic和WebLogic。此外,JavaSoft和資料庫連線的領先提供者Intersolv還合作研製了JDBC-ODBC橋和JDBC驅動程式測試工具包。
有關JDBC最新的信息,有興趣的讀者可以查閱JDBC的官方網站--即JavaSoft的主頁
不足之處
儘管JDBC在JAVA語言層面實現了統一,但不同資料庫仍舊有許多差異。為了更好地實現跨資料庫操作,於是誕生了Hibernate項目,Hibernate是對JDBC的再封裝,實現了對資料庫操作更寬泛的統一和更好的可移植性。
各種方法
1、Oracle8/8i/9i資料庫(thin模式)
Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
String url="jdbc:oracle:thin:@localhost:1521:orcl";
//orcl為資料庫的SID
String user="test";
String password="test";
Connection conn= DriverManager.getConnection(url,user,password);
2、DB2資料庫
Class.forName("com.ibm.db2.jdbc.app.DB2Driver ").newInstance();
String url="jdbc:db2://localhost:5000/sample";
//sample為你的資料庫名
String user="admin";
String password="";
Connection conn= DriverManager.getConnection(url,user,password);
3、Sql Server7.0/2000資料庫
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver").newInstance();
String url="jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=mydb";
//mydb為資料庫
String user="sa";
String password="";
Connection conn= DriverManager.getConnection(url,user,password);
4、Sybase資料庫
Class.forName("com.sybase.jdbc.SybDriver").newInstance();
String url =" jdbc:sybase:Tds:localhost:5007/myDB";
//myDB為你的資料庫名
Properties sysProps = System.getProperties();
SysProps.put("user","userid");
SysProps.put("password","user_password");
Connection conn= DriverManager.getConnection(url, SysProps);
5、Informix資料庫
Class.forName("com.informix.jdbc.IfxDriver").newInstance();
String url =
"jdbc:informix-sqli://123.45.67.89:1533/myDB:INFORMIXSERVER=myserver;
user=testuser;password=testpassword";
//myDB為資料庫名
Connection conn= DriverManager.getConnection(url);
6、MySQL資料庫
Class.forName("org.gjt.mm.mysql.Driver").newInstance();
String url ="jdbc:mysql://localhost/myDB䲁user=soft&password=soft1234&useUnicod
e=true&characterEncoding=8859_1"
//myDB為資料庫名
Connection conn= DriverManager.getConnection(url);
7、PostgreSQL資料庫
Class.forName("org.postgresql.Driver").newInstance();
String url ="jdbc:postgresql://localhost/myDB"
//myDB為資料庫名
String user="myuser";
String password="mypassword";
Connection conn= DriverManager.getConnection(url,user,password);
SQL執行
ResultSet包含符合SQL語句中條件的所有行,並且它通過一套get方法(這些get方法可以訪問當前行中的不同列)提供了對這些行中數據的訪問。ResultSet.next方法用於移動到ResultSet中的下一行,使下一行成為當前行。
下面的代碼段是執行SQL語句的示例。該SQL語句將返回行集合,其中列1為int,列2為String,而列3則為位元組數組:
Java.sql.Statementstmt=conn.createStatement();
ResultSet r=stmt.executeQuery("SELECT a,b,c FROM Table1");
while(r.next()){
//列印當前行的值。
Int i=r.getInt("a");
String s=r.getString("b");
Float f=r.getFloat("c");
System.out.println("ROW="+i+" "+s+" "+f);
}
行和游標
ResultSet維護指向其當前數據行的游標。每調用一次next方法,游標向下移動一行。
最初它位於第一行之前,因此第一次調用next將把游標置於第一行上,使它成為當前行。隨著每次調用next導致游標向下移動一行,按照從上至下的次序獲取ResultSet行。
在ResultSet對象或其父輩Statement對象關閉之前,游標一直保持有效。在SQL中,結果表的游標是有名字的。如果資料庫允許定位更新或定位刪除,則需要將游標的名字作為參數提供給更新或刪除命令。可通過調用方法getCursorName獲得游標名。
DatabaseMetaData.supportsPositionedDelete和supportsPositionedUpdate方法來檢查特定連線是否支持這些操作。當DBMS支持定位更新和刪除操作時,DBMS/驅動程式必須確保適當鎖定選定行,以使定位更新不會導致更新異常或其它並發問題。
列
方法getXXX提供了獲取當前行中某列值的途徑。在每一行內,可按任何次序獲取列值。但為了保證可移植性,應該從左至右獲取列值,並且一次性地讀取列值。
列名或列號可用於標識要從中獲取數據的列。例如,如果ResultSet對象rs的第二列名為"title",並將值存儲為字元串,則下列任一代碼將獲取存儲在該列中的值:
String s=rs.getString("title");
String s=rs.getString(2);
注意列是從左至右編號的,並且從列1開始。同時,用作getXXX方法的輸入的列名不區分大小寫。
提供使用列名這個選項的目的是為了讓在查詢中指定列名的用戶可使用相同的名字作為getXXX方法的參數。另一方面,如果select語句未指定列名(例如在"select * from table1"中或列是導出的時),則應該使用列號。這些情況下,用戶將無法確切知道列名。
有些情況下,SQL查詢返回的結果集中可能有多個列具有相同的名字。如果列名用作getXXX方法的參數,則getXXX將返回第一個匹配列名的值。因而,如果多個列具有相同的名字,則需要使用列索引來確保檢索了正確的列值。這時,使用列號效率要稍微高一些。
關於ResultSet中列的信息,可通過調用方法ResultSet.getMetaData得到。返回的ResultSetMetaData對象將給出其ResultSet對象各列的編號、類型和屬性。
如果列名已知,但不知其索引,則可用方法findColumn得到其列號。
數據類型和轉換
對於getXXX方法,JDBC驅動程式試圖將基本數據轉換成指定Java類型,
然後返回適合的Java值。例如,如果getXXX方法為getString,而基本資料庫中數據類型為VARCHAR,則JDBC驅動程式將把VARCHAR轉換成JavaString。getString的返回值將為JavaString對象。
對非常大的行值使用流
ResultSet可以獲取任意大的LONGVARBINARY或LONGVARCHAR數據。方法getBytes和getString將數據返回為大的塊(最大為Statement.getMaxFieldSize的返回值)。但是,以較小的固定塊獲取非常大的數據可能會更方便,而這可通過讓ResultSet類返回java.io.Input流來完成。從該流中可分塊讀取數據。注意:必須立即訪問這些流,因為在下一次對ResultSet調用getXXX時它們將自動關閉(這是由於基本實現對大塊數據訪問有限制)。
JDBCAPI具有三個獲取流的方法,分別具有不同的返回值:
·getBinaryStream:返回只提供資料庫原位元組而不進行任何轉換的流。
·getAsciiStream返回提供單位元組ASCII字元的流。
·getUnicodeStream返回提供雙位元組Unicode字元的流。
注意:它不同於Java流,後者返回無類型位元組並可(例如)通用於ASCII和Unicode字元。下列代碼演示了getAsciiStream的用法:
Java.sql.Statementstmt=con.createStatement();
ResultSet r=stmt.executeQuery("SELECT x FROM Table2");
//現在以4K塊大小獲取列1結果:
byte buff=newbyte【4096】;
while(r.next()){
Java.io.InputStream fin=r.getAsciiStream(1);
for(;;){
intsize=fin.read(buff);
if(size==-1){//到達流末尾
break;
}
//將新填充的緩衝區傳送到ASCII輸出流:
output.write(buff,0,size);
}
}
NULL結果值
要確定給定結果值是否是JDBC NULL,必須先讀取該列,然後使用ResultSet.wasNull
方法檢查該次讀取是否返回JDBC NULL。
當使用ResultSet.getXXX方法讀取JDBC NULL時,方法wasNull將返回下列值之一:
(1)Javanull值
對於返回Java對象的getXXX方法(例如getString、getBigDecimal、getBytes、getDate、getTime、getTimestamp、getAsciiStream、getUnicodeStream、getBinaryStream、GetObject等)。
(2)零值:對於getByte、getShort、getInt、getLong、getFloat和getDouble。
(3)false值:對於getBoolean。
6. 可選結果集或多結果集
通常使用executeQuery(它返回單個ResultSet)或executeUpdate(它可用於任何資料庫修改語句,並返回更新行數)可執行SQL語句。但有些情況下,應用程式在執行語句之前不知道該語句是否返回結果集。此外,有些已存儲過程可能返回幾個不同的結果集和/或更新計數。
為了適應這些情況,JDBC提供了一種機制,允許應用程式執行語句,然後處理由結果集和更新計數組成的任意集合。這種機制的原理是首先調用一個完全通用的execute方法,然後調用另外三個方法,getResultSet、getUpdateCount和getMoreResults。這些方法允許應用程式一次一個地研究語句結果,並確定給定結果是ResultSet還是更新計數。
用戶不必關閉ResultSet;當產生它的Statement關閉、重新執行或用於從多結果序列中獲取下一個結果時,該ResultSet將被Statement自動關閉。
通用訪問方法
通用資料庫Bean設計
本實例中對資料庫連線和執行SQL語句等通用資料庫操作進行了封裝,通過實現DBConnBean和DBQueryBean兩個JavaBean來完成上述功能。其中DBConnBean負責Java應用程式和資料庫的連線;DBQueryBean提供了一組執行標準SQL的功能,可以實現標準SQL完成的所有功能。其功能代碼分別如下所示:
① DBConnBean.Java的原始碼如下所示:
package dbaccess;
import Java.sql.*;
import Java.util.*;
import Java.io.*;
public class DBConnBean
implements Serializable{
private String DBDriver = "sun.jdbc.odbc.JdbcOdbcDriver";
private String DBHost = "127.0.0.1";
private String DBName = "demo";
private String CONP = "jdbc:odbc:db_demo";
private String username = "";
private String password = "";
private boolean xdebug = true;
public Connection con = null;
public String sql = null;
Statement stmt = null;
public ResultSet result = null;
private int affectedRows = 0;
public DBConnBean()
{
xdebug = true;
con = null;
sql = null;
}
public Connection Connect()
throws Exception
{
String msg = null;
try
{
Class.forName(DBDriver).newInstance();
}
catch(Exception e)
{
msg = "載入資料庫驅動失敗";
if (xdebug) msg += "(驅動'"+DBDriver+"')";
throw new Exception(msg);
}
try
{
String conStr = conp;
con = DriverManager.getConnection(conStr,username,password);
}
catch(SQLException e)
{
msg = "!!資料庫連線失敗";
if (xdebug)
{
msg += "(錯誤信息="" + e.getMessage()+"" SQL狀態值="" + e.getSQLState()+"" 錯誤代碼="" + e.getErrorCode()+"")";
}
throw new Exception(msg);
}
return con;
}
protected void finalize()
throws Throwable
{
super.finalize();
if (stmt != null) stmt.close();
if (result != null) result.close();
}
//最近一次對資料庫查詢受影響的行數
public int getAffectedRows()
{
return affectedRows;
}
public Connection getCon()
{
return con;
}
public String getConp()
{
return conp;
}
public String getDBDriver()
{
return DBDriver;
}
public String getDBName()
{
return DBName;
}
public boolean getDebug()
{
return xdebug;
}
public String getPassword()
{
return password;
}
public ResultSet getResult()
{
return result;
}
public String getSql()
{
return sql;
}
public String GetUserName()
{
return username;
}
public void over()
throws Throwable
{
finalize();
}
public ResultSet query()
throws Exception
{
result = null;
affectedRows = 0;
if (con == null)
Connect();
if (stmt == null)
stmt = con.createStatement();
if (sql.substring(0,6).equalsIgnoreCase("select"))
{
result = stmt.executeQuery(sql);
}
else
{
affectedRows = stmt.executeUpdate(sql);
}
return result;
}
public ResultSet query(String s)
throws Exception
{
sql = s;
return query();
}
public void setDBDriver(String s)
{
DBDriver = s;
}
public void setDebug(boolean b)
{
xdebug = b;
}
public void setgetConp(String s)
{
conp = s;
}
public void setgetDBName(String s)
{
DBName = s;
}
public void setgetUsername(String s)
{
username = s;
}
public void setPassword(String s)
{
password = s;
}
public void setSql(String s)
{
sql = s;
}
}
② DBQueryBean.Java的原始碼如下所示:
package dbaccess;
import Java.sql.*;
import Java.util.*;
import Java.io.*;
import java.lang.reflect.*;
public class DBQueryBean
implements Serializable
{
DBConnBean dbc;
String sql = null;
int rowcount = 0;
int colcount = 0;
// int limitcount = 0;
Vector result = null;
public String _WATCH = "";
public DBQueryBean()
{
dbc = new DBConnBean();
try {
dbc.Connect();
} catch(Exception e) {
handleException(e);
}
}
protected void finalize()
throws Throwable
{
super.finalize();
if (dbc != null) dbc.over();
if (result != null) result.removeAllElements();
}
public String get(int row, int col)
{
if (result==null || row >= result.size()) return null;
String r【】 = (String【】)result.elementAt(row);
if (col >= Java.lang.reflect.Array.getLength(r)) return null;
return r【col】;
}
public int getAffRows() { return dbc.getAffectedRows(); }
public int getColumncount() {
return colcount;
}
public String【】 getRow(int row)
{
if (result==null || row >= result.size()) return null;
return (String 【】)result.elementAt(row);
/*String ret【】 = new String【colcount】;
Vector r = (Vector)result.elementAt(row);
for (int i=0; i<colcount; i++)
ret【i】 = (String)r.elementAt(i);
return ret;*/
}
public int getRowcount() {
return rowcount;
}
public void handleException(Exception e)
{
_WATCH = e.getMessage();
}
public void init()
{
rowcount = 0;
colcount = 0;
// limitcount = 0;
result = null;
}
public void over()
throws Throwable
{
finalize();
}
public int query(String sql)
{
result = new Vector();
int ret = 0;
try {
ResultSet rs = dbc.query(sql);
if (rs == null)
{
ret = dbc.getAffectedRows();
}
else
{
ResultSetMetaData rm = rs.getMetaData();
colcount = rm.getColumnCount();
while (rs.next())
{
String row【】 = new String【colcount】;
for (int i=0; i<colcount; i++)
row【i】 = rs.getString(i+1);
result.addElement(row);
rowcount++;
}
rs.close(); // to release the resource.
ret = result.size();
}
}
catch(Exception e)
{
handleException(e);
return -1;
}
return ret;
}
}
資料庫表結構
本實例中主要出現了三個資料庫表,表名和欄位分別如下所示:
計畫採購表:jhcg_table
欄位名稱 中文名稱 類型 長度
Goods_no 物品編號 vchar 10
Goods_name 物品名稱 Vchar 50
Amount 採購數量 Int
Price 採購單價 float
Gold 幣種 Vchar 15
Units 單位 Vchar 10
Date 時間 Date
Remark 備註 vchar 100
庫存統計表:kctj_table
欄位名稱 中文名稱 類型 長度
Goods_no 物品編號 Vchar 10
Goods_name 物品名稱 Vchar 50
amount 庫存數量 Int
Date 時間 Date
remark 備註 Vchar 100
實際採購表:sjcg_table
欄位名稱 中文名稱 類型 長度
Goods_no 物品編號 Vchar 10
Goods_name 物品名稱 Vchar 50
Amount 採購數量 Int
Price Price 採購單價 Float
Gold 幣種 Vchar 15
Units 採購單位 Vchar 10
Date 時間 Date
Remark 備註 vchar 100
其中業務邏輯非常簡單,即根據計畫採購表和庫存統計表生成實際採購表。同時,對各表完成資料庫的增、刪、改、查等通用操作。
JSP設計
① 插入操作
完成對資料庫表的記錄插入功能,其中計畫採購表的插入主頁面(insert_jhcg.htm)為:
insert_jhcg.htm將用戶輸入傳送給demo_insert_jhcg.jsp,完成插入操作。改jsp檔案的功能代碼為:
<html>
<body>
<jsp:
<jsp:useBean id="DBBean" class="dbaccess.DBQueryBean" scope="page"/>
<hr>
<!--test JavaBean-->
<%
if (DBConn == null||DBBean == null){
out.println("JavaBean not found!");
return;
}
%>
<!--try db_demo connection-->
<%
try{
DBConn.Connect();
}catch(Exception e){
out.println(e.getMessage());
}
%>
<!--execute sql statement-->
<%
String insGoodno = request.getParameter("ed_jhcg_no");
String insGoodname = request.getParameter("ed_jhcg_name");
int insAmount = (Integer.valueOf(request.getParameter("ed_jhcg_amount"))).intValue();
float insPrice = (Float.valueOf(request.getParameter("ed_jhcg_price"))).floatValue();
String insGold = request.getParameter("ed_jhcg_gold");
String insUnit = request.getParameter("ed_jhcg_unit");
String insRemark = request.getParameter("ed_jhcg_remark");
String sqlStatement = "insert into jhcg_table(good_no,good_name,amount,
price,gold,unit,remark) values("+"'"+insGoodno+"'"+","+"'"+insGoodname+"'"+",
"+insAmount+","+insPrice+","+"'"+insGold+"'"+","+"'"+insUnit+"'"+","+"'"+
insRemark+"'"+")";
try{
DBBean.query(sqlStatement);
}catch(Exception e){
out.println(e.getMessage());
}
%>
</body>
</html>
② 查詢操作
該查詢主頁面主要提供對三個資料庫表的條件查詢功能,
query.htm將用戶選擇查詢的資料庫表和查詢條件傳送給demo_query.jsp,由jsp檔案完成資料庫查詢操作和查詢結果集的返回及顯示,其功能代碼如下:
<html>
<body>
<%
String sqlStatement;
String sqlField = "";
String whichTable = "";
String whereClause = "";
String queryNo = "";
String queryName = "";
%>
<jsp:useBean id="DBConn" class="dbaccess.DBConnBean" scope="page"/>
<jsp:useBean id="DBBean" class="dbaccess.DBQueryBean" scope="page"/>
<hr>
<!--test JavaBean-->
<%
if (DBConn == null||DBBean == null){
out.println("JavaBean not found!");
return;
}
%>
<!--try db_demo connection-->
<%
try{
DBConn.Connect();
}catch(Exception e){
out.println(e.getMessage());
}
%>
<!--prepare sql statement-->
<%
String queryRequest = request.getParameter("rb_request");
//out.println("queryRequest:"+queryRequest);
String whichCB = "";
if (queryRequest.equals("1")){
whichCB = "ck_jhcg";
whichTable = "jhcg_table";
queryNo = request.getParameter("ed_jhcg_no");
queryName = request.getParameter("ed_jhcg_name");
if (!queryNo.equals(""))
whereClause = " where good_no="+"'"+queryNo+"'";
if (!queryName.equals("")){
if (!queryNo.equals(""))
whereClause += " and good_name="+"'"+queryName+"'";
else whereClause = " where good_name="+"'"+queryName+"'";
}
}
if (queryRequest.equals("2")){
whichCB = "ck_kctj";
whichTable = "kctj_table";
queryNo = request.getParameter("ed_kctj_no");
queryName = request.getParameter("ed_kctj_name");
if (!queryNo.equals(""))
whereClause = " where good_no="+"'"+queryNo+"'";
if (!queryName.equals("")){
if (!queryNo.equals(""))
whereClause += " and good_name="+"'"+queryName+"'";
else whereClause = " where good_name="+"'"+queryName+"'";
}
}
if (queryRequest.equals("3")){
whichCB = "ck_sjcg";
whichTable = "sjcg_table";
queryNo = request.getParameter("ed_sjcg_no");
queryName = request.getParameter("ed_sjcg_name");
if (!queryNo.equals(""))
whereClause = " where good_no="+"'"+queryNo+"'";
if (!queryName.equals("")){
if (!queryNo.equals(""))
whereClause += " and good_name="+"'"+queryName+"'";
else whereClause = " where good_name="+"'"+queryName+"'";
}
}
String【】 printTitle = request.getParameterValues(whichCB);
%>
<!--create query sql statement-->
<%
sqlStatement = "select ";
for(int i = 0;i<printTitle.length;i++){
sqlField += printTitle【i】+",";
}
sqlStatement += sqlField.substring(0,sqlField.length()-1)+" from "+whichTable;
if (!whereClause.equals(""))
sqlStatement += whereClause;
%>
<!--show query response-->
<%
try{
DBBean.query(sqlStatement);
}catch(Exception e){
out.println("Database Error!");
}
int rows = DBBean.getRowcount();
int cols = DBBean.getColumncount();
%>
<Table align="center" width="80%" border=1>
<tr align=center>
<%
for(int i = 0;i < printTitle.length;i++){
out.println("<td><b>");
out.println(printTitle【i】);
out.println("</b></td>");
}
%>
</tr>
<%
for (int i = 0;i < rows;i++){
out.println("<tr>");
for (int j = 0;j < cols;j++)
out.println("<td>"+DBBean.get(i,j)+"</td>");
out.println("</tr>");
}
%>
</Table>
<br>
<hr>
</body>
</html>
③ 生成實際採購表
生成資料庫表是一個隱式操作,程式根據計畫採購表和庫存統計表的相應欄位生成實際採購表,不需要用戶的任何輸入,其功能代碼如下(demo_create.jsp):
<%@page import="Java.util.*"%>
<html>
<body>
<jsp:useBean id="DBConn" class="dbaccess.DBConnBean" scope="page"/>
<jsp:useBean id="DBBean" class="dbaccess.DBQueryBean" scope="page"/>
<hr>
<!--test JavaBean-->
<%
if (DBConn == null||DBBean == null){
out.println("JavaBean not found!");
return;
}
%>
<!--try db_demo connection-->
<%
try{
DBConn.Connect();
}catch(Exception e){
out.println(e.getMessage());
}
%>
<!--prepare sql statement-->
<%
int amount_jhcg,amount_kctj;
Vector updateRs = new Vector();
DBBean.query("delete * from sjcg_table"); //delete all old records in sjcg_table
DBBean.query("select jhcg_table.good_no,jhcg_table.good_name,jhcg_table.amount,kctj_table.amount,jhcg_table.unit from jhcg_table left join kctj_table on kctj_table.good_no=jhcg_table.good_no");
int rows = DBBean.getRowcount();
int cols = DBBean.getColumncount();
for (int i = 0;i < rows;i++){
String record【】 = new String【4】;
record【0】 = DBBean.get(i,0);
record【1】 = DBBean.get(i,1);
amount_jhcg = (Integer.valueOf(DBBean.get(i,2))).intValue();
if (DBBean.get(i,3) == null) amount_kctj = 0;
else amount_kctj = (Integer.valueOf(DBBean.get(i,3))).intValue();
record【2】 = Integer.toString(amount_jhcg - amount_kctj);
record【3】 = DBBean.get(i,4);
updateRs.addElement(record);
}
for (int i = 0;i < rows;i++){
String insRecord【】 = (String 【】)updateRs.elementAt(i);
String insGoodno,insGoodname,insUnit,insAmount;
insGoodno = insRecord【0】;
insGoodname = insRecord【1】;
insAmount = insRecord【2】;
insUnit = insRecord【3】;
String sqlStatement = "insert into sjcg_table(good_no,good_name,amount,unit) values䲁quot;+"'"+insGoodno+"'"+","+"'"+insGoodname+"'"+","+insAmount+","+"'"+insUnit+"'"+")";
DBBean.query(sqlStatement);
DBBean.query("delete * from sjcg_table where amount<=0");
}
%>
</body>
</html>
上述的開發工具綜合套用介紹了基於Java開發電子商務套用系統的全過程,包括套用開發平台搭建、業務流程分析、JavaBean封裝和JSP開發等內容,其中JSP開發中涉及到了通用SQL(查詢和插入資料庫表)和游標操作(生成實際採購表),基本可以完成任何網路資料庫套用的需求。本實例基本上可以將前面介紹的基於Java的電子商務開發技術串接起來,指導讀者進行電子商務套用開發。
分頁
分頁顯示是Web資料庫套用中經常需要遇到的問題,當用戶的資料庫查詢結果遠遠超過了計算機螢幕的顯示能力的時候,我們該如何合理的將數據呈現給用戶呢?答案就是資料庫分頁顯示,可以完美的解決上述問題。下面是一個資料庫分頁操作的通用實例,對任何資料庫平台上的分頁功能都有很好的借鑑意義。
<%@ page contentType="text/html;charset=8859_1" %>
<%
//變數聲明
Java.sql.Connection sqlCon; //資料庫連線對象
Java.sql.Statement sqlStmt; //SQL語句對象
Java.sql.ResultSet sqlRst; //結果集對象
Java.lang.String strCon; //資料庫連線字元串
Java.lang.String strSQL; //SQL語句
int intPageSize; //一頁顯示的記錄數
int intRowCount; //記錄總數
int intPageCount; //總頁數
int intPage; //待顯示頁碼
Java.lang.String strPage;
int i;
//設定一頁顯示的記錄數
intPageSize = 2;
//取得待顯示頁碼
strPage = request.getParameter("page");
if(strPage==null){//表明在QueryString中沒有page這一個參數,此時顯示第一頁數據
intPage = 1;
}
else{//將字元串轉換成整型
intPage = Java.lang.Integer.parseInt(strPage);
if(intPage<1) intPage = 1;
}
//裝載JDBC驅動程式
Java.sql.DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
//設定資料庫連線字元串
strCon = "jdbc:oracle:thin:@linux:1521:ora4cweb";
//連線資料庫
sqlCon = Java.sql.DriverManager.getConnection(strCon,"hzq","hzq");
//創建一個可以滾動的唯讀的SQL語句對象
sqlStmt = sqlCon.createStatement(Java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE,Java.sql.ResultSet.CONCUR_READ_ONLY);
//準備SQL語句
strSQL = "select name,age from test";
//執行SQL語句並獲取結果集
sqlRst = sqlStmt.executeQuery(strSQL);
//獲取記錄總數
sqlRst.last();
intRowCount = sqlRst.getRow();
//記算總頁數
intPageCount = (intRowCount+intPageSize-1) / intPageSize;
//調整待顯示的頁碼
if(intPage>intPageCount) intPage = intPageCount;
%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>JSP資料庫操作例程 - 數據分頁顯示 - JDBC 2.0 - Oracle</title>
</head>
<body>
<table border="1" cellspacing="0" cellpadding="0">
<tr>
<th>姓名</th>
<th>年齡</th>
</tr>
<%
if(intPageCount>0){
//將記錄指針定位到待顯示頁, 的第一條記錄上
sqlRst.absolute((intPage-1) * intPageSize + 1);
//顯示數據
i = 0;
while(i<intPageSize && !sqlRst.isAfterLast()){
%>
<tr>
<td><%=sqlRst.getString(1)%></td>
<td><%=sqlRst.getString(2)%></td>
</tr>
<%
sqlRst.next();
i++; <, BR>}
}
%>
</table>
第<%=intPage%>頁 共<%=intPageCount%>頁
<%if(intPage<intPageCount){%></a><%}%> <%if(intPage>1){%>
</body>
</html>
<%
//關閉結果集
sqlRst.close();
//關閉SQL語句對象
sqlStmt.close();
//關閉資料庫
sqlCon.close();
%>