一個 Connection
對象,表示了與某個數據源的一條鏈接,數據源的種類能夠是關係型數據庫,文件系統等等之類,只要有對應的 JDBC 驅動,均可以稱之爲數據源。應用程序使用 JDBC API 來維護多條鏈接,這些鏈接可能訪問的是多個數據源,也可能訪問的只是一個數據源。從 JDBC 驅動的角度來看,一個 Connection
對象就意味着一個客戶端會話,一個會話會保持許多狀態,例如用戶 ID,一系列的 SQL Statement 以及結果集,也保存了當前使用的事務處理策略。java
能夠經過如下兩種方式之一來獲取一條鏈接:sql
DriverManager
這個類以及各類各樣的驅動實現DataSource
類更推薦使用 DataSource
對象來獲取鏈接,由於這加強了應用的可移植性,使得代碼更容易維護了,而且使得對鏈接池和分佈式事務的使用更加地透明。全部的 Java EE 組件,都會使用 DataSource 對象來獲取鏈接。數據庫
這一章將會介紹各類不一樣的 JDBC 驅動以及如何使用 Driver
接口、DriverManager
類以及基本的 DataSource
接口。關於鏈接池和分佈式事務的介紹分別在第11章和第12章作介紹。服務器
這種類型的 JDBC 驅動是對另一種訪問 API 的映射,好比說 ODBC,通常須要依賴本地庫,這就致使了它的可移植性不行。JDBC-ODBC 橋就是這種類型的驅動。微信
這種類型的 JDBC 驅動一部分是用 Java 語言寫的,一部分是用本地代碼寫的。這種驅動使用一個本地的客戶端庫來鏈接數據源。因爲對本地代碼的使用,可移植性也不行。網絡
這種類型的驅動使用純 Java 語言編寫,可是通訊的時候須要通過一箇中間件,使用的是與數據庫具體協議無關的獨立協議。這個中間件轉發客戶端的請求給後面的數據源。架構
這種類型的驅動使用純 Java 語言編寫,而且使用網絡協議或者文件 IO 與具體的數據源通訊,客戶端直接與數據源鏈接。app
編寫 JDBC 驅動,必須實現 Driver 接口,而且實現類中必須包含一個靜態初始化塊,當驅動被加載時,這塊代碼會被調用。這塊代碼的主要工做是講本身註冊給 DriverManager,以下代碼所示:分佈式
public class AcmeJdbcDriver implements java.sql.Driver { static { java.sql.DriverManager.registerDriver(new AcmeJdbcDriver()); } }
驅動的實現類必須提供一個無參構造函數,當 DriverManager 想要與 Driver 交互時,它會直接調用它的方法,Driver 接口包含了一個 acceptsURL 方法,DriverManager 能夠調用這個方法來判斷該驅動是否能處理對應的 JDBC URL。函數
當 DriverManager 想要創建一條數據庫鏈接時,它會調用驅動實現類的 connect 方法,並把 URL 做爲參數穿給它,這個方法會返回一個 Connection 對象,或者是當沒法創建數據庫鏈接時,拋出一個 SQLException。若是驅動實現類沒法解析 URL,這個方法將會返回 null。
## 9.2.1 加載一個實現了 java.sql.Driver 接口的驅動類
DriverManager 初始化的時候,會先經過 「java.drivers」 這個系統屬性來嘗試加載驅動,如如下例子:
java -Djdbc.drivers=com.acme.jdbc.AcmeJdbcDriver Test
DriverManager 的 getConnection 方法可以支持 Java SE 的 SPI 服務發現機制,JDBC 4.0 的驅動必須包含如下文件 「META-INF/services/java.sql.Driver」,這個文件會包含實現了 Driver 接口的類名
當 DriverManager 的 deregisterDriver 方法被調用時,若是想要被通知到,那麼 JDBC 驅動就得有對應的實現了 DriverAction 接口的類,DriverAction 的具體實現類並不但願直接被上層應用拿來使用,因此實現 JDBC 驅動的時候,應該將這個類定義爲私有的類,以防止被直接使用。
JDBC 驅動的靜態初始化塊裏面,必須調用 DriverManager.registerDriver(java.sql.Driver, java.sql.DriverAction) 方法,這樣當一個 JDBC 驅動被 DriverManager 註銷的時候,才能被通知到,以下所示:
public class AcmeJdbcDriver implements java.sql.Driver { static DriverAction da; static { java.sql.DriverManager.registerDriver(new AcmeJdbcDriver(), da); } }
DriverManager 類與 Driver 接口一塊兒協做,維護全部可用的 JDBC 驅動。當應用程序經過一個 URL 來獲取一個鏈接的時候, DriverManager 負責找到一個適用該 URL 的驅動,用這個驅動來獲取對應的數據源的鏈接。
DriverManager 的關鍵方法以下所示:
這個方法會將某個驅動加進可用驅動的集合裏,它在一個驅動被裝載的時候隱式地調用,通常狀況下是驅動的靜態代碼塊裏調用這個方法。
這個方法用來獲取一個鏈接,要調用這個方法,必須提供一個 JDBC URL,DriverManager 會使用這個 URL 來輪詢全部已經註冊的驅動,並找到一個能夠識別這個 URL 的驅動,驅動會返回一個 Connection 給 DriverManager,而後再把它交給應用程序。
JDBC URL 的格式以下所示:
jdbc:<subprotocol>:<subname>
subprotocol 定義是要鏈接的是哪一種類型的數據庫,subname 則會根據 subprotoco 的不一樣而不一樣。
如下代碼示範瞭如何從 DriverManager 獲取一個鏈接:
String url = "jdbc:derby:sample"; String user = "SomeUser"; String passwd = "SomePwd"; Connection con = DriverManager.getConnection(url, user, passwd);
DriverManager 類也提供了另一些獲取鏈接的方法:
這個方法適用於不須要提供用戶名和密碼的狀況
這個方法容許在 prop 參數里加入用戶名和密碼,以及其它屬性
DriverPropertyInfo 這個類提供了一個驅動能夠理解的全部的屬性,詳見 Java JDBC API DOC
這個類表明了一個代碼基所擁有的權限。當前惟必定義的權限是 setLog 權限。當一個 Applet 調用了 DriverManager 的 setLogWriter 或者 setLogStream 方法時,SecurityManager 將會檢查是否有權限。若是沒有權限,將會拋出一個 java.lang.SecurityException 異常
DataSource 這個接口是在 JDBC2.0 的可選屬性裏引進的,這是 JDBC 規範推薦的用來獲取數據源鏈接的方式。實現了 DataSource 接口的 JDBC 驅動會返回和經過 DriverManager 獲取的相同的 Connection 實例,使用 DataSource 接口使應用程序更加具備可移植性,由於應用程序不須要爲某個特定的驅動提供相關的鏈接信息,僅僅須要提供一個邏輯的數據源名。邏輯數據源名用來映射到 JNDI 提供的 DataSource 實例。這個 DataSource 實例表明了一個物理上的數據源,並提供獲取相應鏈接的方法。若是關於數據源的屬性或者信息發生了變化,DataSource 對象能夠感知到對應的變化,徹底不須要改變應用代碼。
實現 DataSource 接口時,應該透明地提供如下功能:
還須要注意的是,DataSource 的實現類必須提供一個無參構造函數
接下來的3個小節主要討論:
JDBC API 定義了一系列的屬性來描述 DataSource 的實現,具體的屬性有哪些,取決於具體的 DataSource 實現,也就是說,取決於該實現是一個基本的 DataSource 對象,仍是 ConnectionPoolDataSource,或者是 XADataSource,不管什麼實現,它們都會有共同的屬性 description,如下是標準的 DataSource 屬性:
屬性名 | 數據類型 | 描述 |
---|---|---|
databaseName | String | 數據庫名 |
dataSourceName | String | 數據源名,用來命名底層的 XADataSource 或者是 ConnectionPoolDataSource |
description | String | 對此 DataSource 的描述信息 |
networkProtocol | String | 網絡協議 |
password | String | 數據庫密碼 |
portNumber | int | 數據庫監聽端口 |
roleName | String | 初始 SQL roleName |
serverName | String | 數據庫服務器名 |
user | String | 數據庫用戶名 |
DataSource 的屬性遵循 JavaBean 1.01 規範。具體 DataSource 實現能夠添加屬性,可是不能與原有的有衝突。這些屬性必須提供對應的 setter 和 getter 方法,當一個新的 DataSource 初始化的時候,這些屬性也應該相應進行初始化,如如下代碼所示,這裏的實現是一個 VendorDataSource:
VendorDataSource vds = new VendorDataSource(); vds.setServerName("my_database_server"); String name = vds.getServerName();
DataSource 的屬性,設計的初衷是不該該直接被應用代碼獲取,應該在具體的實現類裏提供獲取的方法,而不是在 DataSource 上定義 public 的屬性,想要獲取屬性值,能夠經過「自省」的方式(反射)來獲取。
Java Naming and Directory Interface (JNDI) API 提供讓應用經過網絡訪問遠程服務的統一方式,本小節將描述如何使用 JNDI 來註冊並訪問一個 JDBC 數據源對象。更詳細的信息能夠查閱 JNDI 規範。
使用 JNDI API,應用能夠經過指定一個邏輯名來訪問一個數據源,在這裏 JNDI 須要使用到命名服務,來將邏輯名映射到對應的數據源。這個特性極大地加強了應用的可移植性,由於不少數據源的配置,能夠在不修改應用層代碼的狀況下進行修改,例如端口號和服務器名。事實上,應用能夠透明地訪問另外一個徹底不一樣的數據源,只須要修改對應的配置。在三層架構的環境中,這個特性很重要,應用服務器會將訪問不一樣數據源的細節隱藏起來,不須要對應用開放。
如下代碼實例瞭如何使用 JNDI 來註冊一個數據源對象:
// Create a VendorDataSource object and set some properties VendorDataSource vds = new VendorDataSource(); vds.setServerName("my_database_server"); vds.setDatabaseName("my_database"); vds.setDescription("data source for inventory and personnel"); // Use the JNDI API to register the new VendorDataSource object. // Reference the root JNDI naming context and then bind the // logical name "jdbc/AcmeDB" to the new VendorDataSource object. Context ctx = new InitialContext(); ctx.bind("jdbc/AcmeDB", vds);
一旦一個 DataSource 註冊在 JNDI 的命名服務後,應用可使用它來獲取一條到物理數據源的鏈接,以下代碼所示:
// Get the initial JNDI naming context Context ctx = new InitialContext(); // Get the DataSource object associated with the logical name // "jdbc/AcmeDB" and use it to obtain a database connection DataSource ds = (DataSource)ctx.lookup("jdbc/AcmeDB"); Connection con = ds.getConnection("user", "pwd");
Connection.close(), Connection.isclosed() 和 Connection.isValid() 這些方法能夠用來關閉一條鏈接和判斷一條鏈接是否還處於活躍狀態。
An application calls the method當應用使用完一條鏈接後,能夠調用 Connection.close() 來關閉這條鏈接,在這條鏈接上全部的 Statement 對象也會被關閉。
一條鏈接關閉後,除了 close(), isClosed() 和 isValid() 方法外,調用其它的方法將會拋出一個 SQLException。
這個方法用來判斷一條鏈接的 close() 方法是否已經被調用過,這個方法不能用來判斷鏈接是否還可用。
可是有寫 JDBC 驅動可能會加強 isClosed() 方法,使得能夠利用這個方法來判斷一條鏈接是否還可用。在這裏,爲了最大的可移植性,應用應該經過 Connection.isValid() 來判斷一條鏈接是否還可用。
這個方法用來標識一條鏈接是否還可用,若是不可用,那麼除了 close(),isClosed() 和isValid() 方法以外,調用其它方法將會拋出 SQLException