JDBC(Java DataBase Connectivity)是使用Java存取數據庫系統的解決方案,它將不同數據庫間各自差異API與標準SQL(Structured Query Language)語(yǔ)句分開(kāi)看待,實(shí)現數據庫無(wú)關(guān)的Java操作接口。開(kāi)發(fā)人員使用JDBC統一的API接口,并專(zhuān)注于標準SQL語(yǔ)句,就可以避免直接處理底層數據庫驅動(dòng)程序與相關(guān)操作接口的差異性。
實(shí)際的數據庫存取是個(gè)非常復雜的主題,可以使用專(zhuān)書(shū)加以說(shuō)明,不過(guò)在本章中,會(huì )告訴您一些JDBC基本API的使用與概念,讓您對Java如何存取數據庫有所認識。
20.1 使用JDBC連接數據庫
在正式使用JDBC進(jìn)行數據庫操作之前,先來(lái)認識JDBC的基本架構,了解數據庫驅動(dòng)程序與數據庫之間的關(guān)系。在這個(gè)小節也將看到,如何設計一個(gè)簡(jiǎn)單的工具類(lèi),讓您在進(jìn)行數據庫連接(Connection)時(shí)更為方便。
20.1.1 簡(jiǎn)介JDBC
如果要連接數據庫并進(jìn)行操作,基本上必須了解數據庫所提供的API操作接口,然而各個(gè)廠(chǎng)商所提供的API操作界面并不一致,如果今天要使用A廠(chǎng)商的數據庫,就必須設計一個(gè)專(zhuān)用的程序來(lái)操作A廠(chǎng)商數據庫所提供的API,將來(lái)如果要使用B廠(chǎng)商的數據庫,即使上層應用程序本身的目的相同,也是要編寫(xiě)專(zhuān)用于B廠(chǎng)商數據庫之存取程序,十分的不方便。
使用JDBC,可由廠(chǎng)商操作實(shí)現操作數據庫接口的驅動(dòng)程序,而Java程序設計人員調用JDBC的API并操作SQL,實(shí)際對數據庫的操作由JDBC驅動(dòng)程序負責。如果要更換數據庫,基本上只要更換驅動(dòng)程序,Java程序中只要加載新的驅動(dòng)程序來(lái)源,即可完成數據庫系統的變更,Java 程序的部分則無(wú)需改變。
圖20-1是JDBC API、數據庫驅動(dòng)程序與數據庫之間的關(guān)系:

圖20-1 應用程序、JDBC與驅動(dòng)程序之間的關(guān)系
簡(jiǎn)單地說(shuō),JDBC希望達到的目的,是讓Java程序設計人員在編寫(xiě)數據庫操作程序的時(shí)候,可以有個(gè)統一的操作接口,無(wú)需依賴(lài)于特定的數據庫API,希望達到“寫(xiě)一個(gè)Java程序,適用所有的數據庫”的目的。
JDBC數據庫驅動(dòng)程序按實(shí)現方式可以分為4個(gè)類(lèi)型:

用戶(hù)的計算機上必須事先安裝好ODBC驅動(dòng)程序,Type 1驅動(dòng)程序利用橋接(Bridge)方式,將JDBC的調用方式轉換為ODBC驅動(dòng)程序的調用方式,如圖20-2所示,Microsoft Access數據庫存取就是使用這種類(lèi)型。

圖20-2 Type 1: JDBC-ODBC Bridge

Type 2驅動(dòng)程序利用橋接方式,驅動(dòng)程序上層封裝Java程序以與Java應用程序作溝通,將JDBC調用轉為本地(Native)程序代碼的調用,下層為本地語(yǔ)言(就像C、C++)來(lái)與數據庫進(jìn)行溝通,下層的函數庫是針對特定數據庫設計的,不像Type 1可以對ODBC架構的數據庫進(jìn)行存取,如圖20-3所示。

圖20-3 Type 2: Native-API Bridge

通過(guò)中間件(middleware)來(lái)存取數據庫,用戶(hù)不必安裝特定的驅動(dòng)程序,而是調用中間件,由中間件來(lái)完成所有的數據庫存取動(dòng)作,然后將結果返回給應用程序,如圖20-4所示。

圖20-4 Type 3: JDBC-moddleware

使用純Java程序來(lái)編寫(xiě)驅動(dòng)程序與數據庫進(jìn)行溝通,而不通過(guò)橋接或中間件來(lái)存取數據庫,如圖20-5所示。

圖20-5 Type 4: Pure Java Driver
在接下來(lái)的內容中,將使用MySQL數據庫系統進(jìn)行操作。MySQL的JDBC驅動(dòng)程序屬于Type 4。您可以在以下的網(wǎng)址獲得MySQL的JDBC驅動(dòng)程序,本章中將使用MySQL Connector/J 5.0。
http://www.mysql.com/products/connector/j/index.html

關(guān)于數據庫系統的使用與操作是個(gè)很大的主題,本書(shū)中并不針對這方面加以介紹,請尋找數據庫系統相關(guān)書(shū)籍自行學(xué)習,不過(guò)為了讓您能順利練習本章的范例,附錄C中包括了一個(gè)MySQL數據庫系統的簡(jiǎn)介,足夠您了解這一章中將用到的一些數據庫操作命令。
20.1.2 連接數據庫
為了要連接數據庫系統,您必須要有JDBC驅動(dòng)程序,由于接下來(lái)將使用MySQL數據庫進(jìn)行操作,所以請將下載的tar.gz文件使用解壓縮軟件解開(kāi),并將其中的mysql-connector-java-*.jar加入至Classpath的設置之中,假設是放在c:\workspace\library\mysql-connector-java-5.0.3-bin.jar,則Classpath中必須有c:\workspace\library\mysql-connector-java-5.0.3-bin.jar這個(gè)路徑設置。
在Java SE中與數據庫操作相關(guān)的JDBC類(lèi)都位于java.sql包中,要連接數據庫,基本上必須有以下幾個(gè)動(dòng)作。

首先必須通過(guò)java.lang.Class類(lèi)的forName()動(dòng)態(tài)加載驅動(dòng)程序類(lèi),并向DriverManager注冊JDBC驅動(dòng)程序(驅動(dòng)程序會(huì )自動(dòng)通過(guò)DriverManager.registerDriver()方法注冊)。MySQL的驅動(dòng)程序類(lèi)是com.mysql.jdbc.Driver,一個(gè)加載JDBC驅動(dòng)程序的程序片段如下所示:
try { Class.forName("com.mysql.jdbc.Driver");}
catch(ClassNotFoundException e) { System.out.println("找不到驅動(dòng)程序類(lèi)");}

JDBC URL定義了連接數據庫時(shí)的協(xié)議、子協(xié)議、數據源識別。
協(xié)定:子協(xié)定:數據源識別
“協(xié)議”在JDBC中總是jdbc開(kāi)始;“子協(xié)議”是橋接的驅動(dòng)程序或是數據庫管理系統名稱(chēng),使用MySQL的話(huà)是 "mysql";“數據源識別”標出找出數據庫來(lái)源的地址與連接端口。舉個(gè)例子來(lái)說(shuō),MySQL的JDBC URL編寫(xiě)方式如下:
jdbc:mysql://主機名稱(chēng):連接端口/數據庫名稱(chēng)?參數=值&參數=值
主機名稱(chēng)可以是本機localhost或是其他連接主機,連接端口為3306,假如要連接demo數據庫,并指明用戶(hù)名稱(chēng)與密碼,可以如下指定:
jdbc:mysql://localhost:3306/demo?user=root&password=123
如果要使用中文存取的話(huà),還必須給定參數useUnicode及characterEncoding,表明是否使用Unicode,并指定字符編碼方式,例如:
jdbc:mysql://localhost:3306/demo?user=root&password=123&useUnicode=true&characterEncoding=Big5

要連接數據庫,可以向java.sql.DriverManager要求并獲得java.sql.Connection對象。Connection是數據庫連接的具體代表對象,一個(gè)Connection對象就代表一個(gè)數據庫連接,您可以使用DriverManager的getConneciton()方法,指定JDBC URL作為自變量并獲得Connection對象:
try {String url = "jdbc:mysql://localhost:3306/demo?" +
"user=root&password=123";
Connection conn = DriverManager.getConnection(url);
....
}
catch(SQLException e) {....
}
java.sql.SQLException是在處理JDBC時(shí)經(jīng)常遇到的異常對象,SQLException是受檢異常 (Checked Exception),您必須使用try...catch或throws明確處理,它表示JDBC操作過(guò)程中若發(fā)生錯誤時(shí)的具體對象代表。
獲得Connection對象之后,可以使用isClosed()方法測試與數據庫的連接是否關(guān)閉,在操作完數據庫之后,如果確定不再需要連接,則必須使用close()來(lái)關(guān)閉與數據庫的連接,以釋放連接時(shí)相關(guān)的必要資源。
getConnection()方法可以在參數上指定用戶(hù)名稱(chēng)與密碼,例如:
String url = "jdbc:mysql://localhost:3306/demo";
String user = "root";
String password = "123";
Connection conn = DriverManager.getConnection(url,
user, password);
20.1.3 簡(jiǎn)單的Connection工具類(lèi)
在20.1.2節獲得Connection的程序片段中,您可以看到其中直接用字符串在程序中寫(xiě)下JDBC URL、用戶(hù)名稱(chēng)與密碼等信息,實(shí)際的程序并不會(huì )將這些敏感信息寫(xiě)在程序代碼之中,而且這么做的話(huà),如果要更改用戶(hù)名稱(chēng)或密碼時(shí),還要修改程序、重新編譯,在程序維護上并不方便。
您可以將JDBC URL、用戶(hù)名稱(chēng)與密碼等設置信息編寫(xiě)在一個(gè)屬性文件中,由程序讀取這個(gè)屬性文件中的信息,如果需要變更信息,則只要修改屬性文件,無(wú)須修改程序、重新編譯。在Java SE中,屬性文件的讀取可以交給java.util.Properties類(lèi)。
舉個(gè)實(shí)際的例子,假設您使用了以下命令在MySQL中建立了demo數據庫:
CREATE DATABASE demo;
由于獲得Connection的方式,按所使用的環(huán)境及程序需求而有所不同,因而您可以先設計一個(gè)DBSource接口,規范獲得Connection的方法,如范例20.1所示。
Ü 范例20.1 DBSource.java
package onlyfun.caterpillar;
import java.sql.Connection;
import java.sql.SQLException;
public interface DBSource {
public Connection getConnection() throws SQLException;
public void closeConnection(Connection conn) throws SQLException;
}
接著(zhù)可以實(shí)現DBSource接口,您的目的是從屬性文件中讀取設置信息、加載JDBC驅動(dòng)程序,可以通過(guò)getConnection()方法獲得Connection對象,并通過(guò)closeConnection()方法關(guān)閉Connection對象,在這里以一個(gè)簡(jiǎn)單的SimpleDBSource類(lèi)作為示范,如范例20.2所示。
Ü 范例20.2 SimpleDBSource.java
package onlyfun.caterpillar;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class SimpleDBSource implements DBSource {
private Properties props;
private String url;
private String user;
private String passwd;
public SimpleDBSource() throws IOException,
ClassNotFoundException {
this("jdbc.properties");
}
public SimpleDBSource(String configFile) throws IOException,
ClassNotFoundException {
props = new Properties();
props.load(new FileInputStream(configFile));
url = props.getProperty("onlyfun.caterpillar.url");
user = props.getProperty("onlyfun.caterpillar.user");
passwd = props.getProperty("onlyfun.caterpillar.password");
Class.forName(
props.getProperty("onlyfun.caterpillar.driver"));
}
public Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, user, passwd);
}
public void closeConnection(Connection conn) throws SQLException {
conn.close();
}
}
默認的構造函數設置中,是讀取jdbc.properties文件中的設置,如果打算自行指定屬性文件名稱(chēng),則可以使用另一個(gè)有參數的構造函數。Properties的getProperty()方法會(huì )讀取屬性文件中的“鍵(Key)”對應的“值(Value)”,假設您的屬性文件設置如下:
Ü 范例20.3 jdbc.properties
onlyfun.caterpillar.driver=com.mysql.jdbc.Driver
onlyfun.caterpillar.url=jdbc:mysql://localhost:3306/demo
onlyfun.caterpillar.user=root
onlyfun.caterpillar.password=123456
DBSource的getConnection()方法簡(jiǎn)單地從DriverManager的getConnection()方法獲得Connection對象,而closeConnection()方法則是將給定的Connection關(guān)閉。就簡(jiǎn)單的連接程序來(lái)說(shuō),這樣已經(jīng)足夠,不過(guò)待會(huì )還會(huì )介紹連接池(Connection pool)的概念,到時(shí)將會(huì )修改一下DBSource的getConnection()和closeConnection()方法,以達到重復使用Connection、節省資源的目的。
最后,范例20.4使用一個(gè)簡(jiǎn)單的程序來(lái)測試SimpleDBSource是否可以正確地獲得與數據庫的連接,以及是否正確地關(guān)閉連接。
Ü 范例20.4 ConnectionDemo.java
package onlyfun.caterpillar;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
public class ConnectionDemo {
public static void main(String[] args) {
try {
DBSource dbsource = new SimpleDBSource();
Connection conn = dbsource.getConnection();
if(!conn.isClosed()) {
System.out.println("數據庫連接已開(kāi)啟…");
}
dbsource.closeConnection(conn);
if(conn.isClosed()) {
System.out.println("數據庫連接已關(guān)閉…");
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
如果您的demo數據庫已建立,并正確設置jdbc.properties中的相關(guān)信息,則應該能看到以下的執行結果:
數據庫連接已開(kāi)啟…
數據庫連接已關(guān)閉…
20.1.4 簡(jiǎn)單的連接池(Connection pool)
在數據庫應用程序中,數據庫連接的獲得是一個(gè)耗費時(shí)間與資源的操作,包括了建立Socket connection、交換數據(用戶(hù)密碼驗證、相關(guān)參數)、會(huì )話(huà)(Session)、日志(Logging)、分配進(jìn)程(Process)等資源。
如果數據庫的操作是很頻繁的動(dòng)作,則要考慮到重復使用連接的需求,以節省在獲得連接時(shí)的時(shí)間與資源,通常會(huì )實(shí)現一個(gè)連接池(Connection pool),有需要連接時(shí)可以從池中獲得,不需要連接時(shí)就將連接放回池中,而不是直接關(guān)閉連接。
這里將實(shí)現一個(gè)簡(jiǎn)單的連接池,示范連接池中,重復使用連接的基本概念,范例20.5使用java.util.ArrayList來(lái)實(shí)現連接池,可以將先前使用過(guò)的連接放到ArrayList對象中,下一次需要連接時(shí)則直接從ArrayList中獲得。
Ü 范例20.5 BasicDBSource.java
package onlyfun.caterpillar;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
public class BasicDBSource implements DBSource {
private Properties props;
private String url;
private String user;
private String passwd;
private int max; // 連接池中最大Connection數目
private List<Connection> connections;
public BasicDBSource() throws IOException, ClassNotFoundException {
this("jdbc.properties");
}
public BasicDBSource(String configFile) throws IOException,
ClassNotFoundException {
props = new Properties();
props.load(new FileInputStream(configFile));
url = props.getProperty("onlyfun.caterpillar.url");
user = props.getProperty("onlyfun.caterpillar.user");
passwd = props.getProperty("onlyfun.caterpillar.password");
max = Integer.parseInt(
props.getProperty("onlyfun.caterpillar.poolmax"));
Class.forName(
props.getProperty("onlyfun.caterpillar.driver"));
connections = new ArrayList<Connection>();
}
public synchronized Connection getConnection()
throws SQLException {
if(connections.size() == 0) {
return DriverManager.getConnection(url, user, passwd);
}
else {
int lastIndex = connections.size() - 1;
return connections.remove(lastIndex);
}
}
public synchronized void closeConnection(Connection conn)
throws SQLException {
if(connections.size() == max) {
conn.close();
}
else {
connections.add(conn);
}
}
}
BasicDBSource也實(shí)現DBSource接口,考慮這個(gè)類(lèi)可能在多線(xiàn)程的環(huán)境中使用,因此在getConnection()與closeConnection()方法上使用syhchronized加以修飾。在獲得連接時(shí),如果目前池中沒(méi)有Connection對象,則新建立一個(gè)連接,如果有存在的Connection對象,則從池中移出。
BasicDBSource可以設置連接池中最大Connection保存數量,如果超過(guò)這個(gè)數量,則傳入closeConnection()方法的Connection對象直接關(guān)閉,否則就放入連接池中,以在下一次需要數據庫連接時(shí)直接使用。范例20.6是個(gè)測試BasicDBSource的簡(jiǎn)單程序。
Ü 范例20.6 ConnectionPoolDemo.java
package onlyfun.caterpillar;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
public class ConnectionPoolDemo {
public static void main(String[] args) {
try {
DBSource dbsource = new BasicDBSource("jdbc2.properties");
Connection conn1 = dbsource.getConnection();
dbsource.closeConnection(conn1);
Connection conn2 = dbsource.getConnection();
System.out.println(conn1 == conn2);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
這里所使用的設置文件是jdbc2.properties,當中多了連接池最大數量之設置,如下所示:
Ü 范例20.7 jdbc2.properties
onlyfun.caterpillar.driver=com.mysql.jdbc.Driver
onlyfun.caterpillar.url=jdbc:mysql://localhost:3306/demo
onlyfun.caterpillar.user=root
onlyfun.caterpillar.password=123456
onlyfun.caterpillar.poolmax=10
程序中獲得Connection之后,將之使用closeConnection()方法關(guān)閉,但實(shí)際上closeConnection()方法并不是真正使用Connection的close()方法,而是放回池中,第二次獲得Connection時(shí),所獲得的是先前放入池中的同一對象,因此執行的結果會(huì )顯示true。
在更復雜的情況下,您還需要考慮到初始的Connection數量、Connection最大idle的數量、如果超過(guò)多久時(shí)間,要回收多少數量的Connection等問(wèn)題。實(shí)際上也不需要自行設計連接池的程序,現在網(wǎng)絡(luò )上有不少優(yōu)秀的開(kāi)放原始碼連接池程序,例如Proxool(http://proxool.sourceforge.net/index.html)或Apache Jakarta的Common DBCP(http://jakarta.apache.org/commons/dbcp/),您可以自行參考官方網(wǎng)站上的相關(guān)文件,了解它們是如何設置與使用的。
聯(lián)系客服