JDBC(Java DataBase Connectivity)是使用Java存取數(shù)據(jù)庫系統(tǒng)的解決方案,它將不同數(shù)據(jù)庫間各自差異API與標(biāo)準(zhǔn)SQL(Structured Query Language)語句分開看待,實現(xiàn)數(shù)據(jù)庫無關(guān)的Java操作接口。開發(fā)人員使用JDBC統(tǒng)一的API接口,并專注于標(biāo)準(zhǔn)SQL語句,就可以避免直接處理底層數(shù)據(jù)庫驅(qū)動程序與相關(guān)操作接口的差異性。 實際的數(shù)據(jù)庫存取是個非常復(fù)雜的主題,可以使用專書加以說明,不過在本章中,會告訴您一些JDBC基本API的使用與概念,讓您對Java如何存取數(shù)據(jù)庫有所認(rèn)識。 20.1 使用JDBC連接數(shù)據(jù)庫 在正式使用JDBC進行數(shù)據(jù)庫操作之前,先來認(rèn)識JDBC的基本架構(gòu),了解數(shù)據(jù)庫驅(qū)動程序與數(shù)據(jù)庫之間的關(guān)系。在這個小節(jié)也將看到,如何設(shè)計一個簡單的工具類,讓您在進行數(shù)據(jù)庫連接(Connection)時更為方便。 20.1.1 簡介JDBC 如果要連接數(shù)據(jù)庫并進行操作,基本上必須了解數(shù)據(jù)庫所提供的API操作接口,然而各個廠商所提供的API操作界面并不一致,如果今天要使用A廠商的數(shù)據(jù)庫,就必須設(shè)計一個專用的程序來操作A廠商數(shù)據(jù)庫所提供的API,將來如果要使用B廠商的數(shù)據(jù)庫,即使上層應(yīng)用程序本身的目的相同,也是要編寫專用于B廠商數(shù)據(jù)庫之存取程序,十分的不方便。 使用JDBC,可由廠商操作實現(xiàn)操作數(shù)據(jù)庫接口的驅(qū)動程序,而Java程序設(shè)計人員調(diào)用JDBC的API并操作SQL,實際對數(shù)據(jù)庫的操作由JDBC驅(qū)動程序負責(zé)。如果要更換數(shù)據(jù)庫,基本上只要更換驅(qū)動程序,Java程序中只要加載新的驅(qū)動程序來源,即可完成數(shù)據(jù)庫系統(tǒng)的變更,Java 程序的部分則無需改變。 圖20-1是JDBC API、數(shù)據(jù)庫驅(qū)動程序與數(shù)據(jù)庫之間的關(guān)系: 圖20-1 應(yīng)用程序、JDBC與驅(qū)動程序之間的關(guān)系 簡單地說,JDBC希望達到的目的,是讓Java程序設(shè)計人員在編寫數(shù)據(jù)庫操作程序的時候,可以有個統(tǒng)一的操作接口,無需依賴于特定的數(shù)據(jù)庫API,希望達到“寫一個Java程序,適用所有的數(shù)據(jù)庫”的目的。 JDBC數(shù)據(jù)庫驅(qū)動程序按實現(xiàn)方式可以分為4個類型: Type 1:JDBC-ODBC Bridge 用戶的計算機上必須事先安裝好ODBC驅(qū)動程序,Type 1驅(qū)動程序利用橋接(Bridge)方式,將JDBC的調(diào)用方式轉(zhuǎn)換為ODBC驅(qū)動程序的調(diào)用方式,如圖20-2所示,Microsoft Access數(shù)據(jù)庫存取就是使用這種類型。 圖20-2 Type 1: JDBC-ODBC Bridge Type 2:Native-API Bridge Type 2驅(qū)動程序利用橋接方式,驅(qū)動程序上層封裝Java程序以與Java應(yīng)用程序作溝通,將JDBC調(diào)用轉(zhuǎn)為本地(Native)程序代碼的調(diào)用,下層為本地語言(就像C、C++)來與數(shù)據(jù)庫進行溝通,下層的函數(shù)庫是針對特定數(shù)據(jù)庫設(shè)計的,不像Type 1可以對ODBC架構(gòu)的數(shù)據(jù)庫進行存取,如圖20-3所示。 圖20-3 Type 2: Native-API Bridge Type 3:JDBC-middleware 通過中間件(middleware)來存取數(shù)據(jù)庫,用戶不必安裝特定的驅(qū)動程序,而是調(diào)用中間件,由中間件來完成所有的數(shù)據(jù)庫存取動作,然后將結(jié)果返回給應(yīng)用程序,如圖20-4所示。 圖20-4 Type 3: JDBC-moddleware Type 4:Pure Java Driver 使用純Java程序來編寫驅(qū)動程序與數(shù)據(jù)庫進行溝通,而不通過橋接或中間件來存取數(shù)據(jù)庫,如圖20-5所示。 圖20-5 Type 4: Pure Java Driver 在接下來的內(nèi)容中,將使用MySQL數(shù)據(jù)庫系統(tǒng)進行操作。MySQL的JDBC驅(qū)動程序?qū)儆赥ype 4。您可以在以下的網(wǎng)址獲得MySQL的JDBC驅(qū)動程序,本章中將使用MySQL Connector/J 5.0。 http://www./products/connector/j/index.html
關(guān)于數(shù)據(jù)庫系統(tǒng)的使用與操作是個很大的主題,本書中并不針對這方面加以介紹,請尋找數(shù)據(jù)庫系統(tǒng)相關(guān)書籍自行學(xué)習(xí),不過為了讓您能順利練習(xí)本章的范例,附錄C中包括了一個MySQL數(shù)據(jù)庫系統(tǒng)的簡介,足夠您了解這一章中將用到的一些數(shù)據(jù)庫操作命令。 20.1.2 連接數(shù)據(jù)庫 為了要連接數(shù)據(jù)庫系統(tǒng),您必須要有JDBC驅(qū)動程序,由于接下來將使用MySQL數(shù)據(jù)庫進行操作,所以請將下載的tar.gz文件使用解壓縮軟件解開,并將其中的mysql-connector-java-*.jar加入至Classpath的設(shè)置之中,假設(shè)是放在c:\workspace\library\mysql-connector-java-5.0.3-bin.jar,則Classpath中必須有c:\workspace\library\mysql-connector-java-5.0.3-bin.jar這個路徑設(shè)置。 在Java SE中與數(shù)據(jù)庫操作相關(guān)的JDBC類都位于java.sql包中,要連接數(shù)據(jù)庫,基本上必須有以下幾個動作。 加載JDBC驅(qū)動程序 首先必須通過java.lang.Class類的forName()動態(tài)加載驅(qū)動程序類,并向DriverManager注冊JDBC驅(qū)動程序(驅(qū)動程序會自動通過DriverManager.registerDriver()方法注冊)。MySQL的驅(qū)動程序類是com.mysql.jdbc.Driver,一個加載JDBC驅(qū)動程序的程序片段如下所示: try { Class.forName("com.mysql.jdbc.Driver"); } catch(ClassNotFoundException e) { System.out.println("找不到驅(qū)動程序類"); } 提供JDBC URL JDBC URL定義了連接數(shù)據(jù)庫時的協(xié)議、子協(xié)議、數(shù)據(jù)源識別。 協(xié)定:子協(xié)定:數(shù)據(jù)源識別 “協(xié)議”在JDBC中總是jdbc開始;“子協(xié)議”是橋接的驅(qū)動程序或是數(shù)據(jù)庫管理系統(tǒng)名稱,使用MySQL的話是 "mysql";“數(shù)據(jù)源識別”標(biāo)出找出數(shù)據(jù)庫來源的地址與連接端口。舉個例子來說,MySQL的JDBC URL編寫方式如下: jdbc:mysql://主機名稱:連接端口/數(shù)據(jù)庫名稱?參數(shù)=值&參數(shù)=值 主機名稱可以是本機localhost或是其他連接主機,連接端口為3306,假如要連接demo數(shù)據(jù)庫,并指明用戶名稱與密碼,可以如下指定: jdbc:mysql://localhost:3306/demo?user=root&password=123 如果要使用中文存取的話,還必須給定參數(shù)useUnicode及characterEncoding,表明是否使用Unicode,并指定字符編碼方式,例如: jdbc:mysql://localhost:3306/demo?user=root&password=123&useUnicode=true&characterEncoding=Big5 獲得Connection 要連接數(shù)據(jù)庫,可以向java.sql.DriverManager要求并獲得java.sql.Connection對象。Connection是數(shù)據(jù)庫連接的具體代表對象,一個Connection對象就代表一個數(shù)據(jù)庫連接,您可以使用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時經(jīng)常遇到的異常對象,SQLException是受檢異常 (Checked Exception),您必須使用try...catch或throws明確處理,它表示JDBC操作過程中若發(fā)生錯誤時的具體對象代表。 獲得Connection對象之后,可以使用isClosed()方法測試與數(shù)據(jù)庫的連接是否關(guān)閉,在操作完數(shù)據(jù)庫之后,如果確定不再需要連接,則必須使用close()來關(guān)閉與數(shù)據(jù)庫的連接,以釋放連接時相關(guān)的必要資源。 getConnection()方法可以在參數(shù)上指定用戶名稱與密碼,例如: String url = "jdbc:mysql://localhost:3306/demo"; String user = "root"; String password = "123"; Connection conn = DriverManager.getConnection(url, user, password); 20.1.3 簡單的Connection工具類 在20.1.2節(jié)獲得Connection的程序片段中,您可以看到其中直接用字符串在程序中寫下JDBC URL、用戶名稱與密碼等信息,實際的程序并不會將這些敏感信息寫在程序代碼之中,而且這么做的話,如果要更改用戶名稱或密碼時,還要修改程序、重新編譯,在程序維護上并不方便。 您可以將JDBC URL、用戶名稱與密碼等設(shè)置信息編寫在一個屬性文件中,由程序讀取這個屬性文件中的信息,如果需要變更信息,則只要修改屬性文件,無須修改程序、重新編譯。在Java SE中,屬性文件的讀取可以交給java.util.Properties類。 舉個實際的例子,假設(shè)您使用了以下命令在MySQL中建立了demo數(shù)據(jù)庫: CREATE DATABASE demo; 由于獲得Connection的方式,按所使用的環(huán)境及程序需求而有所不同,因而您可以先設(shè)計一個DBSource接口,規(guī)范獲得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; } 接著可以實現(xiàn)DBSource接口,您的目的是從屬性文件中讀取設(shè)置信息、加載JDBC驅(qū)動程序,可以通過getConnection()方法獲得Connection對象,并通過closeConnection()方法關(guān)閉Connection對象,在這里以一個簡單的SimpleDBSource類作為示范,如范例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(); } } 默認(rèn)的構(gòu)造函數(shù)設(shè)置中,是讀取jdbc.properties文件中的設(shè)置,如果打算自行指定屬性文件名稱,則可以使用另一個有參數(shù)的構(gòu)造函數(shù)。Properties的getProperty()方法會讀取屬性文件中的“鍵(Key)”對應(yīng)的“值(Value)”,假設(shè)您的屬性文件設(shè)置如下: Ü 范例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()方法簡單地從DriverManager的getConnection()方法獲得Connection對象,而closeConnection()方法則是將給定的Connection關(guān)閉。就簡單的連接程序來說,這樣已經(jīng)足夠,不過待會還會介紹連接池(Connection pool)的概念,到時將會修改一下DBSource的getConnection()和closeConnection()方法,以達到重復(fù)使用Connection、節(jié)省資源的目的。 最后,范例20.4使用一個簡單的程序來測試SimpleDBSource是否可以正確地獲得與數(shù)據(jù)庫的連接,以及是否正確地關(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("數(shù)據(jù)庫連接已開啟…"); }
dbsource.closeConnection(conn);
if(conn.isClosed()) { System.out.println("數(shù)據(jù)庫連接已關(guān)閉…"); }
} catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } } 如果您的demo數(shù)據(jù)庫已建立,并正確設(shè)置jdbc.properties中的相關(guān)信息,則應(yīng)該能看到以下的執(zhí)行結(jié)果: 數(shù)據(jù)庫連接已開啟… 數(shù)據(jù)庫連接已關(guān)閉… 20.1.4 簡單的連接池(Connection pool) 在數(shù)據(jù)庫應(yīng)用程序中,數(shù)據(jù)庫連接的獲得是一個耗費時間與資源的操作,包括了建立Socket connection、交換數(shù)據(jù)(用戶密碼驗證、相關(guān)參數(shù))、會話(Session)、日志(Logging)、分配進程(Process)等資源。 如果數(shù)據(jù)庫的操作是很頻繁的動作,則要考慮到重復(fù)使用連接的需求,以節(jié)省在獲得連接時的時間與資源,通常會實現(xiàn)一個連接池(Connection pool),有需要連接時可以從池中獲得,不需要連接時就將連接放回池中,而不是直接關(guān)閉連接。 這里將實現(xiàn)一個簡單的連接池,示范連接池中,重復(fù)使用連接的基本概念,范例20.5使用java.util.ArrayList來實現(xiàn)連接池,可以將先前使用過的連接放到ArrayList對象中,下一次需要連接時則直接從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數(shù)目 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也實現(xiàn)DBSource接口,考慮這個類可能在多線程的環(huán)境中使用,因此在getConnection()與closeConnection()方法上使用syhchronized加以修飾。在獲得連接時,如果目前池中沒有Connection對象,則新建立一個連接,如果有存在的Connection對象,則從池中移出。 BasicDBSource可以設(shè)置連接池中最大Connection保存數(shù)量,如果超過這個數(shù)量,則傳入closeConnection()方法的Connection對象直接關(guān)閉,否則就放入連接池中,以在下一次需要數(shù)據(jù)庫連接時直接使用。范例20.6是個測試BasicDBSource的簡單程序。 Ü 范例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(); }
} } 這里所使用的設(shè)置文件是jdbc2.properties,當(dāng)中多了連接池最大數(shù)量之設(shè)置,如下所示: Ü 范例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)閉,但實際上closeConnection()方法并不是真正使用Connection的close()方法,而是放回池中,第二次獲得Connection時,所獲得的是先前放入池中的同一對象,因此執(zhí)行的結(jié)果會顯示true。 在更復(fù)雜的情況下,您還需要考慮到初始的Connection數(shù)量、Connection最大idle的數(shù)量、如果超過多久時間,要回收多少數(shù)量的Connection等問題。實際上也不需要自行設(shè)計連接池的程序,現(xiàn)在網(wǎng)絡(luò)上有不少優(yōu)秀的開放原始碼連接池程序,例如Proxool(http://proxool./index.html)或Apache Jakarta的Common DBCP(http://jakarta./commons/dbcp/),您可以自行參考官方網(wǎng)站上的相關(guān)文件,了解它們是如何設(shè)置與使用的。 |
|