JDBC(Java data Base Connectivity)是Java語言爲了支持SQL功能而提供的與數據庫相聯的用戶接口,JDBC中包括了一組由Java語言書寫的接口和類,它們都是獨立於特定的DBMS,或者說它們可以和各種數據相關聯。有了JDBC以後,程序員可以方便地在Java語言中使用SQL語言,從而使Java應用程序或Java applet可以實現對分佈在網絡上的各種關係數據庫的訪問。使用了JDBC以後,程序員可以將精力集中於上層的功能實現,而不必關心底層與具體的DBMS的連接和訪問過程。
11.1 關係數據庫簡介
關係數據庫系統的思想是由IBM公司的E.F.Codd於1970年率先提出的。
關係數據庫支持的數據模型是關係模型。關係模型的基本條件是其關係模式中每個屬性值必須是一個不可分割的數據量。簡單地說,在關係數據庫中數據結構一般是張兩維表,這張兩維表的每一列的值必須是不可分割的數據項,而任兩行應互不相同。
關係模型的主要特點是:
⑴結構簡單,表示力強,易於掌握。
⑵語言一體化,非過程化的操作,方便用戶使用。
⑶有一定的數學理論作基礎,利於進一步研究和發展。
⑷操作耗時,執行效率低。
關係數據庫管理系統一般向用戶提供數據檢索、數據插入、數據刪除、數據修改四種基本操作功能。
11.2 JDBC概述
JDBC由一組Java語言編寫的類和接口組成,使用內嵌式的SQL,主要實現三方面的功能:建立與數據庫的連接,執行SQL聲明以及處理SQL執行結果。JDBC支持基本的SQL功能,使用它可方便地與不同的關係型數據庫建立連接,進行相關操作,並無需再爲不同的DBMS分別編寫程序。下面我們先介紹JDBC與SQL及ODBC的關係,再介紹JDBC支持的兩種模型,最後介紹JDBC的抽象接口和數據庫驅動器Driver。
11.2.1 JDBC與SQL
儘管一般數據庫系統在很大範圍內支持SQL的語義語法,但它們在複雜的高層次功能如存儲功能調用和外部連接等方面往往不盡一致。爲了解決這一矛盾,JDBC採用以下的幾種措施:
(1)JDBC允許使用從屬於DBMS的系統的任何詢問語句,這些詢問語句都將被傳送給後臺的DBMS實際執行。這樣增加了使用的靈活性,一個應用程序的詢問可以不是SQL形式的,而是SQL的特定引出形式,如爲特定的DBMS提供的文件或圖片查詢。這樣做的風險則在某些DBMS中可以會出現錯誤。
(2)一般認爲ANSI SQL Entry Level的功能比較完備,而且是被廣泛支持的。因此爲了使JDBC與SQL一致,要求用戶使用至少ANSI SQL 92 Entry Level以上的版本,這樣就給那些要求廣泛的可攜帶性的應用程序提供了共同命名的保證。
(3)對較複雜的應用,JDBC提供了接口類DatabaseMetadata,用戶可通過這個類獲取相應DBMS的描述信息,再根據所得信息採取特定的查詢操作方式。
11.2.2 JDBC與ODBC
Microsoft的ODBC(Open DataBase Connectivity)是當前與關係型數據庫連接最常用的接口。JDBC是建立在ODBC的基礎上的,實際上可視爲ODBD的Java語言翻譯形式。當然兩者都是建立在X/Open SQL CLI(Call Level Interface)的抽象定義之上的。而JDBC與ODBC相比,在使用上更爲方便。
既然ODBC已經是成型的通用接口,我們可在Java程序中直接使用ODBC卻要建立JDBC接口,這樣做的原因和好處包括以下幾點:
(1)ODBC使用的是C語言界面,而從Java直接調用C源碼容易在安全性、健壯性和可移植性等方面產生問題,運行功效也受到影響。
(2)將ODBC的C語言API逐字譯爲Java也並不理想。比如,Java沒有指針,ODBC的一種面向對象式的翻譯界面,對Java的程序員來說更爲自然方便。
(3)ODBC難於學習掌握,經常將簡單的特性與複雜的特性混合使用。而JDBC相對簡單明瞭許多,容易理解掌握。
(4)JDBC有助於實現“純Java“的方案。當使用ODBC時,每一臺客戶機都要求裝入ODBC的驅動器和管理器。而當使用JDBC,驅動器完全由Java語言編寫時,JDBC代碼可以在所有的Java平臺上自動裝入、移植,而且是安全的。
當然,在JDBC也可以使用ODBC,但是要通過中介JDBC-ODBC Bridge使用。
11.2.3 JDBC支持的兩種模型
在與數據庫的連接操作中,JDBC支持兩種不同的模型。這兩川模型根據用戶與數據庫的關係層次不同,分別稱爲兩模型和三層模型。
兩層模型中,Java的應用程序(Applet或Application)直接與數據庫聯繫。用戶的SQL聲明被提交給數據庫,執行的結果回送給用戶,如下圖所示。這種模型具有客戶機/服務器結構,用戶的機器如同客戶機,存放數據庫的機器則如同服務器,連接兩者的可以是局域網,也可以是廣域網。
┌─────────┐
│Java Application │
│ Java Applet │ Clinet Machine
├─────────┤
│ JDBC │
└─────────┘
↑
↓
┌─────────┐
│ DBMS │ Database Serve
└─────────┘
圖11.1 JDBC支持的兩層模型
在三層模型中,用戶不直接與數據庫聯繫。用戶的命令首先發送給一個所謂“中間層”,中間層再將SQL聲明發給DMBS。執行的結果也同樣由中間層轉交,如圖11.2所示。三層模型的好處是,可以通過中間層保持對存取權限和公有數據允許修改類型的控制,便於安全管理。同時,用戶可以使用一種較爲友善的高層API,由中間層轉化爲恰當的低層命令,保證較好地運行功效。到目前爲止,中間層多用C或C++語言編寫。隨着Java語言的逐步推廣,將出現用Java編寫的中間層,更好地利用它的健壯性、多線程,安全性等特點。
┌──────────┐
│ Java Applet │
│ HTML Browse │
└──────────┘
↑
↓
┌──────────┐
│Application Server │
│ (Java) │
├──────────┤
│ JDBC │
└──────────┘
↑
↓
┌──────────┐
│ DBMS │
└──────────┘
圖11.2 JDBC支持的三層模型
11.2.4 JDBC的抽象接口
JDBC中最重要的部分是定義了一系列的抽象接口,通過這些接口,JDBC實現了三個基本的功能:建立與數據的連接、執行SQL聲明和處理執行結果。
這些接口都存在Java的sql包中,它們的名稱和基本功能是:
*java.sql.DriverMagnager
管理驅動器,支持驅動器與數據連接的創建。
*java.sql.Connection
代表與某一數據庫的連接,支持SQL聲明的創建。
*java.sql.Statement
在連接中執行一靜態的SQL聲明並取得執行結果。
*java.sql.PreparedStatement
Statement的子類,代表預編譯的SQL聲明。
*java.sql.CallableStatement
Statement的子類,代表SQL的存儲過程。
java.sql.ResultSet
代表執行SQL聲明後產生的數據結果。
11.2.5 JDBC的數據庫驅動器Driver
Java的應用程序員通過sql包中定義的一系列抽象類對數據庫進行操作,而實現這些抽象類,實際完成操作,則是由數據庫驅動器Driver運行的。它們之間的層次關係如圖11.3所示
┌─────────┐
│Java Application │
└─────────┘
JDBC API ————————
┌─────────┐
│JDBC Manager │
└─────────┘
——————————————
┌─────┐┌───-─--─┐┌─────┐┌───────-┐
DJBC │JDBC-Net ││JDBC-ODBC | │Native-API││Native-Protocol |
Drivers│ Driver ││Bridge Driver││ Driver │ | Driver |
└─────┘└───-──--┘└─────┘└─────-──┘
↓ ↓ ↓ ↓
┌───────────────────────────────────┐
│ D B M S |
───────────────────────────────────-┘
圖11.3 JDBC Drivers
JDBC的Driver可分爲以下四種類型:
(1)JDBC-ODBC Bridge和ODBC Driver
這種驅動器器通過ODBC驅動器提供數據庫連接。使用這種驅動器,要求每一臺客戶機都裝入ODBC的驅動器。
(2)Native-API partly-Java Driver
這種驅動器將JDBC指令轉化成所連接使用的DBMS的操作形式。各客戶機使用的數據庫可能是Oracle,可能是Sybase,也可能是Access,都需要在客戶機上裝有相應DBMS的驅動程序。
(3)JDBC-Net All-Java Driver
這種驅動器將JDBC指令轉化成獨立於DBMS的網絡協議形式,再由服務器轉化爲特定DBMS的協議形式。有關DBMS的協議由各數據庫廠商決定。這種驅動器可以聯接到不同的數據庫上,最爲靈活。目前一些廠商已經開始添加JDBC的這種驅動器到他們已有的數據庫中介產品中。要注意的是,爲了支持廣域網存取,需要增加有關安全性的措施,如防火牆等等。
(4)Native-protocol All-Java Driver
這種驅動器將JDBC指令轉化成網絡協議後不再轉換,由DBMS直接使用。相當於客戶機直接與服務器聯繫,對局域網適用。
在這四種驅動器中,後兩類“純Java”(All-Java)的驅動器效率更高,也更具有通用性。但目前第一、第二類驅動器比較容易獲得,使用也較普遍。
16.3 JDBC編程
11.3.1 程序基本結構
一般的JDBC程序都完成三項功能:與數據庫建立連接;傳送SQL 聲明以及對返回結果進行處理。下面我們通過一個具體例子說明這三項功能的實現過程。
例11.1 Creage.java給出了一個簡單的JDBC程序,此程序執行後創建一張名爲testTable的表,表中包括兩個域,域名分別爲id和name。
1: import java.net.URL;
2: import java.sql.*;
3:
4: class Create{
5: public static void main (String[] args){
6: String url="jdbc:odbc:demo";
7: String query="CREATE TABLE testTable" + "(id INT,name CHAR(10))";
8:
9: try{
10: //下載jdbc-odbc bridge 驅動器
11: Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");//關於此句參見下面Cyclone的註釋
//與驅動器建立連接 ,這裏可使用不同的特定驅動,如jdbcfororacle或jdbcforsqlserver
14: Connection con=DriverManager.getConnection(url,"user","password");
15:
16: //創建一個Statement對象
17: Statement stmt=con.createStatement();
18:
19: //執行SQL聲明
20: stmt.executeUpdate(query);
21: System.out.println("Create successfully!");
22:
23: //關閉 stm
24: stmt.close();
25:
26: //關閉連接
27: con.close();
28: }catch(SQLException ex){
//SQL異常信息
29:System.out.println("/n***SQLException caught ***/n");
30: while(ex!=null){
31: System.out.println("SQLState:"+ex.getSQLState());
32: System.out.println("Message:"+ex.getMessage());
33: System.out.println("Vendor:"+ex.getErrorCode());
34: ex=ex.getNextException();
35: System.out.println("");
}
36:}catch(java.lang.Exception ex){
37:ex.printStackTrace();
38:}
39:}
40:}
11.3.2 Statement類及其子類
1)Statement接口
Statement stmt=con.createStatement();
//執行SQL聲明
int count1=stmt.executeUpdate("INSERT INTO testTable(id,name) VALUES(1,'wu')");
int count2=stmt.executeUpdate("INSERT INTO testTable(id,name) VALUES(2,'wang')");
2)PreparedStatement接口
String data[][]={{"5","xu"},{"6","yan"}};
PreparedStatement pstmt=con.prepareStatement("INSERT INTO testTable (id,name) VALUES(?,?)");
//參數賦值,執行SQL聲明
for (int i=0;i<data.length;i++){
pstmt.setInt(1,Integer.parseInt(data[i][0]));
pstmt.setString(2,data[i][1]);
pstmt.executeUpdate();
}
3)CallableStatement接口
CallableStatement cstmt=con.prepareCall("{call Search(?)}");
//參數賦值
cstmt.setInt(1,934678);
//執行儲存過程。
cstmt.execute();
11.3.3 結果集ResultSet及ResultSetMetaData
String query = "SELECT * FROM testTable";
Statement stmt=con.createStatement();
//發出查詢要求,獲得結果集
ResultSet rs=stmt.executeQuery(query);
//顯示結果集各行各列
System.out.println("The detail of testTable is:");
ResultSetMetaData rsmd=rs.getMetaData();
//獲得結果集列數
in numCols=rsmd.getColumnCount();
//顯示列標題
for(int i=1;i<=numCols;i++)
{
if(i>1) System.out.print(",");
System.out.print(rsmd.getColumnLabel(i));
}
System.out.println("");
//顯示結果集信息
while(rs.next())
{
//顯示一行
for(int i=1;i<=numCols;i++)
{
if(i>1) System.out.print(",");
System.out.print(rs.getString(i));
}
11.3.4 DatabaseMetaData
DatabaseMetaData dma = con.getMetaData();
//驅動器和URL信息
System.out.println("/nConnected to" + dma.getURL());
System.out.println("Driver" + dma.getDriverName());
System.out.println("Version" + dma.getDriverVersion());
//數據庫信息
System.out.println("/nDataBase name:" + dma.getDatabaseProductName()+dma.getDatabaseProductVersion());
System.out.println("DataBase supports SQL keywords:/n/t" + dma.getSQLKeywords());
//數據庫功能信息函數
dma.supportsANSI92EntryLevelSQL()
dma.supportsANSI92FullSQL()
dma.supportsStoredProcedures()
11.3.5 JDBC數據類型及類型轉換
一、JDBC的數據類型
JDBC的sql包中除了與數據庫連接有關的抽象接口及與驅動器有關的DriverManager、DriverPropertyInfo等類型外,還定義了若干數據類,用以代表數據庫中可能用到的SQL類型。下面我們就對它們逐一進行簡略介紹。
1、sql.Date
sql包中的日期類Date是util包中Date類的子類,實際上也是util.Date類的子集。它只處理年月日,而忽略小時和分秒,用以代表SQL的DATE信息。
Date類的構造方法爲:
public Date(int year, int mouth, int day)
其中參數格式同util.Date類的構造方法一樣,年參數爲所需設定的年份減去1900所得的整數值,月參數爲0至11,日參數爲1至31。如1998年1月23日所對應創建日期類的方法調用爲:
Date d=new Date(98,0,23);
Date類還提供兩個與String類互相轉換的方法,分別是:
public static Date valueOf(String s)
將字符串類參數轉換爲日期類對象。其中String類參數S的格式爲“年-月-日”,加“1997-04-12”。
public String toString()
將日期類對象轉換爲String類對象表示,同樣採用“年-月-日”的格式。
2、sql.Time
該類是util.Date類的子類,也是它的一個子集。在Time類裏,只處理小時和分秒,代表SQL的TIME類型。它與sql.Date合起來才表示完整的util.Date類信息。
Time類的構造方法爲:
public Time(int hour,int minute,int second)
其中小時參數值爲0至23,分秒參數取值均爲0至59。
與sql.Date一樣,Time類也定義了兩個與String類互相轉換的函數ValueOf和String。不同的是String類對象的格式爲“小時:分:秒”,如“12:26:06”。
3、sql.Timestamp
這個類也是util.Date類的子類,其中除了包含年月日、小時和分秒和信息之外,還加入了納秒信息(nanosecond),1納秒即1毫微秒。Timestamp類用來代表SQL時間戳(Timestamp)類型信息。
Timestamp類的構造方法爲:
public Timestamp(int year, int mouth, int date, int hour, int minute, int second, int nano)其中納秒參數的取值從0至999,999,999,其餘各參數同前。
Timestamp類特別定義了設置和獲得納秒信息的方法,分別是
public getnanos()
獲取時間戳的納秒部分
public void setNanos(int n)
以給定數值設置時間戳的納秒部分
4、sql.Types
Types類是Object類的直接子類。在這個類中以靜態常量的形式定義了可使用的SQL的數值類型。所有這些類型常量都以前綴
public final static int
的形式標明是公有靜態整數,且不可改動。具體的類型名和含義如表11.1所示。其中OTHER用來代表數據庫定義的特殊數據,可以用getObject或setObject方法將其映射爲一個Java的Object對象。
表11.1 Types中定義的SQL類型
類型名 |
含義 |
BIGINT |
長整型數 |
BINARY |
二進制數 |
BIT |
比特數 |
CHAR |
字符型 |
DATE |
日期型 |
DECIMAL |
十進制數 |
DOUBLE |
雙精度數 |
FLOAT |
浮點數 |
INTEGER |
整數 |
LONGVARBINARY |
可變長型二進制數 |
LONGVARCHAR |
可變長型字符 |
NULL |
空類型 |
NUMERIC |
數值型 |
OTHER |
其他類型 |
REAL |
實數 |
SMALLINT |
短整型 |
TIME |
時間類型 |
TIMESTAMP |
時間戳類型 |
TINYINT |
微整型 |
VARBINARY |
可變二進制數 |
VARCHAR |
可變字符型 |
二、SQL與Java
由於SQL數據類型與Java的數據類型不一致,因而在使用Java類型的應用程序與使用SQL類型的數據庫之間,需要某種讀寫類型轉換機制。實際上我們前面介紹的ResultSet類的“get”系列方法,Statement及其子類的“set“系列方法和registerOutParameter方法,都是這一轉換機制的組成部分。
需要進行的讀寫轉換包括三種情況:
第一種情況是從數據庫中讀取數值後,存放在ResultSet對象中的是SQL類型的數據。而調用“get”系列方法時,JDBC纔將SQL類型轉換爲指定的Java類型。在一般情形下,SQL類型相對應的Java類型如表11-2所示。
表11.2 SQL類型一般所對應的Java類型
SQL type |
Java type |
CHAR |
java.lang.String |
VARCHAR |
java.lang.String |
LONGVARCHAR |
java.lang.String |
NUMERIC |
java.lang.Bignum |
DECIMAL |
java.lang.Bignum |
BIT |
boolean |
TINYINT |
byte |
SMALLINT |
short |
INTEGER |
int |
BIGINT |
long |
REAL |
float |
FLOAT |
double |
DOUBLE |
double |
BINARY |
byte[] |
VARBINARY |
byte[] |
LONGVARBINARY |
byte[] |
DATE |
java.sql.Date |
TIME |
java.sql.Time |
TIMESTAMP |
java.sql.Timestamp |
當然,在使用時用戶可以指定將SQL類型轉換爲某個需要的特定類型而不遵循表11.2。例如在結果集中的某個FLOAT型數值,依標準轉換應用使用getDouble方法獲取,但實際上按用戶的不同需求也可以使用getFloat,getInt,甚至gefByte方法獲取,但只是有可能影響數值精確度。表11.3列出了對每一SQL類型可用以獲取“get”方法的清單,其中“+”表示可以使用該方法,“*”表示最好使用該方法。
表11.3 獲取SQL類型使用的“get”方法
/ |
T |
S |
I |
B |
R |
F |
D |
D |
N |
B |
C |
V |
L |
B |
V |
L |
D |
T |
T |
getByte |
* |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
|
|
|
|
|
|
getShort |
+ |
* |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
|
|
|
|
|
|
getInt |
+ |
+ |
* |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
|
|
|
|
|
|
getLong |
+ |
+ |
+ |
* |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
|
|
|
|
|
|
getFloat |
+ |
+ |
+ |
+ |
* |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
|
|
|
|
|
|
getDouble |
+ |
+ |
+ |
+ |
+ |
* |
* |
+ |
+ |
+ |
+ |
+ |
+ |
|
|
|
|
|
|
getBignum |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
* |
* |
+ |
+ |
+ |
+ |
|
|
|
|
|
|
getBoolean |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
* |
+ |
+ |
+ |
|
|
|
|
|
|
getString |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
* |
* |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
getBytes |
|
|
|
|
|
|
|
|
|
|
|
|
|
* |
* |
+ |
|
|
|
getDate |
|
|
|
|
|
|
|
|
|
|
+ |
+ |
+ |
|
|
|
* |
|
+ |
getTime |
|
|
|
|
|
|
|
|
|
|
+ |
+ |
+ |
|
|
|
|
* |
+ |
getTimestamp |
|
|
|
|
|
|
|
|
|
|
+ |
+ |
+ |
|
|
|
+ |
|
* |
getAsciiStream |
|
|
|
|
|
|
|
|
|
|
+ |
+ |
* |
+ |
+ |
+ |
|
|
|
getUnicodeStream |
|
|
|
|
|
|
|
|
|
|
+ |
+ |
* |
+ |
+ |
+ |
|
|
|
getBinaryStream |
|
|
|
|
|
|
|
|
|
|
|
|
|
+ |
+ |
* |
|
|
|
getObject |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
注: “+”表示允許使用;“*”表示推薦使用
第二種情形是當用戶發出的SQL操作通過PrepareStatement和CallableStatement執行,帶有向數據庫輸入的參數時,需使用這些聲明類的“set”系列方法。例如對PrepareStatement類對象pstmt ,調用方法
pstmt.setLong(1,2222222)
驅動器將自動把2222222轉換爲SQL的BIGINT類型數據,發往數據庫。表16-4給出在這一情形下Java數據轉換爲SQL數據的標準。
表11.4 Java類型所對應轉換的SQL類型
Java type |
SQL type |
java.lang.String |
VARCHAR or LONGVARCHAR |
java.lang.Bignum |
NUMERIC |
boolean |
BIT |
byte |
TINYINT |
short |
SMALLINT |
int |
INTEGER |
long |
BIGINT |
float |
REAL |
double |
DOUBLE |
byte[] |
VARBINARY or LONGVARBINARY |
java.sql.Date |
DATE |
java.sql.Time |
TIME |
java.sql.Timestamp |
TIMESTAMP |
最後一種情形是在使用CallableStatement時,用戶可能用到INOUT參數。這時的轉換過程最爲複雜。首先,使用“set”系列方法給這些參數賦值,驅動器使用表11.4所示的標準映射將Java類型數據換爲SQL類型,再發往數據庫。其次,使用CallableStatement的registerOutParameter方法,爲每一個參數登記其作爲返回值時的SQL類型。這一登記類型必須是在sql.Types中定義的名稱,如表11.1所示。最後在執行完畢後使用“get”方法取回參數的結果值。
下面的程序片段給出了一次INOUT參數從賦值到取值的完整轉換過程,其中cstmt是CallableStatement的實例對象,有兩個INOUT參數:
cstmt.setByte(1,25);//參數賦值
cstmt.setLong(2,4678935);
cstmt.registerOutParameter(1,java.sql.Types.TINYINT);
cstmt.registerOutParameter(2,java.sql.Type.BIGINT);//返回類型類型登記
ResultSet rs=cstmt.executeUpdate();
while(rs.next()){...//處理結果}
byte x=cstmt.getByte(1);//取回參數輸出值;
long l=cstmt.getLong(2);
我們可以寫出這兩個INOUT參數的類型轉換流程如下所示:
setByte executeUpdate getByte
↓ ↓ ↓
參數1 byte———→TINYINT————→TINYINT———→byte
setLong executeUpdate getLong
↓ ↓ ↓
參數2 long———→BIGINT————→BIGINT———→long
圖11.12參數轉換流程圖
最後要提醒用戶注意的是,INOUT參數的“get”方法和“set”方法的類型應當是一致的,如例中參數1的setByte和getByte方法。