一般情況下,在應(yīng)用程序中進(jìn)行數(shù)據(jù)庫連接,調(diào)用JDBC接口,首先要將特定廠商的JDBC驅(qū)動實現(xiàn)加載到系統(tǒng)內(nèi)存中,然后供系統(tǒng)使用。基本結(jié)構(gòu)圖如下:
驅(qū)動加載入內(nèi)存的過程
這里所謂的驅(qū)動,其實就是實現(xiàn)了java.sql.Driver接口的類。如oracle的驅(qū)動類是 oracle.jdbc.driver.OracleDriver.class(此類可以在oracle提供的JDBC jar包中找到),此類實現(xiàn)了java.sql.Driver接口。
由于驅(qū)動本質(zhì)上還是一個class,將驅(qū)動加載到內(nèi)存和加載普通的class原理是一樣的:使用Class.forName("driverName")。以下是將常用的數(shù)據(jù)庫驅(qū)動加載到內(nèi)存中的代碼:
//加載Oracle數(shù)據(jù)庫驅(qū)動
Class.forName("oracle.jdbc.driver.OracleDriver");
//加載SQL Server數(shù)據(jù)庫驅(qū)動
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
//加載MySQL 數(shù)據(jù)庫驅(qū)動
Class.forName("com.mysql.jdbc.Driver");
注意:Class.forName()將對應(yīng)的驅(qū)動類加載到內(nèi)存中,然后執(zhí)行內(nèi)存中的static靜態(tài)代碼段,代碼段中,會創(chuàng)建一個驅(qū)動Driver的實例,放入DriverManager中,供DriverManager使用。
例如,在使用Class.forName() 加載oracle的驅(qū)動oracle.jdbc.driver.OracleDriver時,會執(zhí)行OracleDriver中的靜態(tài)代碼段,創(chuàng)建一個OracleDriver實例,然后調(diào)用DriverManager.registerDriver()注冊:
static {
Timestamp localTimestamp = Timestamp.valueOf("2000-01-01 00:00:00.0");
try {
if (defaultDriver == null) {
//創(chuàng)建一個OracleDriver實例,然后注冊到DriverManager中
defaultDriver = new OracleDriver();
DriverManager.registerDriver(defaultDriver);
}
} catch (RuntimeException localRuntimeException) {
} catch (SQLException localSQLException) {
}
Driver的功能
java.sql.Driver接口規(guī)定了Driver應(yīng)該具有以下功能:
其中:
acceptsURL(String url) 方法用來測試對指定的url,該驅(qū)動能否打開這個url連接。driver對自己能夠連接的url會制定自己的協(xié)議,只有符合自己的協(xié)議形式的url才認(rèn)為自己能夠打開這個url,如果能夠打開,返回true,反之,返回false;
例如:oracle定義的自己的url協(xié)議如下:
jdbc:oracle:thin:@//<host>:<port>/ServiceName
jdbc:oracle:thin:@<host>:<port>:<SID>
oracle自己的acceptsURL(String url)方法如下:
public boolean acceptsURL(String paramString) {
if (paramString.startsWith("jdbc:oracle:")) {
return (oracleDriverExtensionTypeFromURL(paramString) > -2);
}
return false;
}
private int oracleDriverExtensionTypeFromURL(String paramString) {
int i = paramString.indexOf(58) + 1;
if (i == 0) {
return -2;
}
int j = paramString.indexOf(58, i);
if (j == -1) {
return -2;
}
if (!(paramString.regionMatches(true, i, "oracle", 0, j - i))) {
return -2;
}
++j;
int k = paramString.indexOf(58, j);
if (k == -1) {
return -3;
}
String str = paramString.substring(j, k);
if (str.equals("thin")) {
return 0;
}
if ((str.equals("oci8")) || (str.equals("oci"))) {
return 2;
}
return -3;
}
由上可知oracle定義了自己應(yīng)該接收什么類型的URL,自己能打開什么類型的URL連接(注意:這里acceptsURL(url)只會校驗url是否符合協(xié)議,不會嘗試連接判斷url是否有效)
connect(String url,Properties info)方法,創(chuàng)建Connection對象,用來和數(shù)據(jù)庫的數(shù)據(jù)操作和交互,而Connection則是真正數(shù)據(jù)庫操作的開始(在此方法中,沒有規(guī)定是否要進(jìn)行acceptsURL()進(jìn)行校驗)。
手動加載驅(qū)動 Driver 并實例化進(jìn)行數(shù)據(jù)庫操作的例子
public static void driverTest(){
try {
//1.加載oracle驅(qū)動類,并實例化
Driver driver = (Driver) Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
//2.判定指定的URL oracle驅(qū)動能否接受(符合oracle協(xié)議規(guī)則)
boolean flag = driver.acceptsURL("jdbc:oracle:thin:@127.0.0.1:1521:xe");
//標(biāo)準(zhǔn)協(xié)議測試
boolean standardFlag1 = driver.acceptsURL("jdbc:oracle:thin:@//<host>:<port>/ServiceName");
boolean standardFlag2 = driver.acceptsURL("jdbc:oracle:thin:@<host>:<port>:<SID>");
System.out.println("協(xié)議測試:"+flag+"\t"+standardFlag1+"\t"+standardFlag2);
//3.創(chuàng)建真實的數(shù)據(jù)庫連接:
String url = "jdbc:oracle:thin:@127.0.0.1:1521:xe";
Properties props = new Properties();
props.put("user", "louluan");
props.put("password", "123456");
Connection connection = driver.connect(url, props);
//connection 對象用于數(shù)據(jù)庫交互,代碼省略。。。。。
} catch (Exception e) {
System.out.println("加載Oracle類失敗!");
e.printStackTrace();
} finally{
}
}
上述的手動加載Driver并且獲取連接的過程稍顯笨拙:如果現(xiàn)在我們加載進(jìn)來了多個驅(qū)動Driver,那么手動創(chuàng)建Driver實例,并根據(jù)URL進(jìn)行創(chuàng)建連接就會顯得代碼雜亂無章,并且還容易出錯,并且不方便管理。JDBC中提供了一個DriverManager角色,用來管理這些驅(qū)動Driver。
DriverManager角色
事實上,一般我們操作Driver,獲取Connection對象都是交給DriverManager統(tǒng)一管理的。DriverManger可以注冊和刪除加載的驅(qū)動程序,可以根據(jù)給定的url獲取符合url協(xié)議的驅(qū)動Driver或者是建立Conenction連接,進(jìn)行數(shù)據(jù)庫交互。
以下是DriverManager的關(guān)鍵方法摘要:
DriverManager 內(nèi)部持有這些注冊進(jìn)來的驅(qū)動 Driver,由于這些驅(qū)動都是 java.sql.Driver 類型,那么怎樣才能獲得指定廠商的驅(qū)動Driver呢?答案就在于:
java.sql.Driver接口規(guī)定了廠商實現(xiàn)該接口,并且定義自己的URL協(xié)議。廠商們實現(xiàn)的Driver接口通過acceptsURL(String url)來判斷此url是否符合自己的協(xié)議,如果符合自己的協(xié)議,則可以使用本驅(qū)動進(jìn)行數(shù)據(jù)庫連接操作,查詢驅(qū)動程序是否認(rèn)為它可以打開到給定 URL 的連接。
使用DriverManager獲取指定Driver
對于驅(qū)動加載后,如何獲取指定的驅(qū)動程序呢?這里,DriverManager的靜態(tài)方法getDriver(String url)可以通過傳遞給的URL,返回可以打開此URL連接的Driver。
比如,我想獲取oracle的數(shù)據(jù)庫驅(qū)動,只需要傳遞形如jdbc:oracle:thin:@<host>:<port>:<SID>或者jdbc:oracle:thin:@//<host>:<port>/ServiceName的參數(shù)給DriverManager.getDriver(String url)即可:
Driver oracleDriver =DriverManager.getDriver("jdbc:oracle:thin:@<host>:<port>:<SID>");
實際上,DriverManager.getDriver(String url)方法是根據(jù)傳遞過來的URL,遍歷它維護(hù)的驅(qū)動Driver,依次調(diào)用驅(qū)動的Driver的acceptsURL(url),如果返回acceptsURL(url)返回true,則返回對應(yīng)的Driver:
public static Driver getDriver(String paramString) throws SQLException {
//省略部分代碼。。。。
Iterator localIterator = registeredDrivers.iterator();
//遍歷注冊的驅(qū)動
while (localIterator.hasNext()) {
DriverInfo localDriverInfo = (DriverInfo) localIterator.next();
if (isDriverAllowed(localDriverInfo.driver, localClass))
try {
//如果accepsURL() 為true,返回對應(yīng)的driver
if (localDriverInfo.driver.acceptsURL(paramString)) {
//返回對應(yīng)的driver
return localDriverInfo.driver;
}
} catch (SQLException localSQLException) {
}
else
println(" skipping: "+ localDriverInfo.driver.getClass().getName());
}
throw new SQLException("No suitable driver", "08001");
//-----省略部分代碼
}
使用DriverManager注冊和取消注冊驅(qū)動Driver
在本文開始的 驅(qū)動加載的過程中,討論了當(dāng)使用Class.forName("driverName")加載驅(qū)動的時候,會向DriverManager中注冊一個Driver實例。以下代碼將驗證此說法:
public static void defaultDriver(){
try {
String url = "jdbc:oracle:thin:@127.0.0.1:1521:xe";
//1.將Driver加載到內(nèi)存中,然后執(zhí)行其static靜態(tài)代碼,創(chuàng)建一個OracleDriver實例注冊到DriverManager中
Class.forName("oracle.jdbc.driver.OracleDriver");
//取出對應(yīng)的oracle 驅(qū)動Driver
Driver driver = DriverManager.getDriver(url);
System.out.println("加載類后,獲取Driver對象:"+driver);
//將driver從DriverManager中注銷掉
DriverManager.deregisterDriver(driver);
//重新通過url從DriverManager中取Driver
driver = DriverManager.getDriver(url);
System.out.println(driver);
} catch (Exception e) {
System.out.println("加載Oracle類失敗!");
e.printStackTrace();
} finally{
}
}
以上代碼主要分以下幾步:
1. 首先是將 oracle.jdbc.driver.OracleDriver加載到內(nèi)存中;
2. 然后便調(diào)用DriverManager.getDriver()去取Driver實例;
3. 將driver實例從DriverManager中注銷掉;
4.嘗試再取 對應(yīng)url的Driver實例;
上述代碼執(zhí)行的結(jié)果如下:
從執(zhí)行結(jié)果看,正好能夠驗證以上論述:當(dāng)?shù)谒牟皆俅潍@取對應(yīng)url的 Driver 實例時,由于已經(jīng)被注銷掉了,找不到適當(dāng)?shù)尿?qū)動Driver,拋出了 "Not suitable driver" 的異常。
將上述的例子稍作變化,在注銷掉了靜態(tài)塊創(chuàng)建的driver后,往DriverManager注冊一個自己創(chuàng)建的Driver對象實例(具體步驟請看注釋):
public static void defaultDriver(){
try {
String url = "jdbc:oracle:thin:@127.0.0.1:1521:xe";
//1.將Driver加載到內(nèi)存中,然后執(zhí)行其static靜態(tài)代碼,創(chuàng)建一個OracleDriver實例注冊到DriverManager中
Driver dd = (Driver)Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
//2.取出對應(yīng)的oracle 驅(qū)動Driver
Driver driver = DriverManager.getDriver(url);
System.out.println("加載類后,獲取Driver對象:"+driver);
//3. 將driver從DriverManager中注銷掉
DriverManager.deregisterDriver(driver);
//4.此時DriverManager中已經(jīng)沒有了驅(qū)動Driver實例,將創(chuàng)建的dd注冊到DriverManager中
DriverManager.registerDriver(dd);
//5.重新通過url從DriverManager中取Driver
driver = DriverManager.getDriver(url);
System.out.println("注銷掉靜態(tài)創(chuàng)建的Driver后,重新注冊的Driver: "+driver);
System.out.println("driver和dd是否是同一對象:" +(driver==dd));
} catch (Exception e) {
System.out.println("加載Oracle類失敗!");
e.printStackTrace();
} finally{
}
}
以下代碼運行的結(jié)果:
以上代碼先創(chuàng)建了一個Driver對象,在注銷了DriverManager中由加載驅(qū)動過程中靜態(tài)創(chuàng)建驅(qū)動之后,注冊到系統(tǒng)中,現(xiàn)在DriverManager中對應(yīng)url返回的Driver 即是在代碼中創(chuàng)建的Driver對象。
使用DriverManager創(chuàng)建 Connection 連接對象
創(chuàng)建 Connection 連接對象,可以使用驅(qū)動Driver的 connect(url,props),也可以使用 DriverManager 提供的getConnection()方法,此方法通過url自動匹配對應(yīng)的驅(qū)動Driver實例,然后調(diào)用對應(yīng)的connect方法返回Connection對象實例。
Driver driver = DriverManager.getDriver(url);
Connection connection = driver.connect(url, props);
上述代碼等價于:
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection connection = DriverManager.getConnection(url, props);
jdbc.drivers
DriverManager 作為 Driver 的管理器,它在第一次被使用的過程中(即在代碼中第一次用到的時候),它會被加載到內(nèi)存中,然后執(zhí)行其定義的static靜態(tài)代碼段,在靜態(tài)代碼段中,有一個 loadInitialDrivers() 靜態(tài)方法,用于加載配置在jdbc.drivers 系統(tǒng)屬性內(nèi)的驅(qū)動Driver,配置在jdbc.drivers 中的驅(qū)動driver將會首先被加載:
static {
loadInitialDrivers();//加載配置在jdbc.drivers系統(tǒng)變量中的驅(qū)動driver
println("JDBC DriverManager initialized");
SET_LOG_PERMISSION = new SQLPermission("setLog");
}
private static void loadInitialDrivers() {
String str1;
try {
str1 = (String) AccessController
.doPrivileged(new PrivilegedAction() {
public String run() {
return System.getProperty("jdbc.drivers");//返回jdbc.drivers值
}
});
} catch (Exception localException1) {
str1 = null;
}
//省略部分代碼......
if ((str1 == null) || (str1.equals("")))
return;
String[] arrayOfString1 = str1.split(":");
println("number of Drivers:" + arrayOfString1.length);
for (String str2 : arrayOfString1)
try {
//Class.forName加載對應(yīng)的driver
Class.forName(str2, true, ClassLoader.getSystemClassLoader());
} catch (Exception localException2) {
println("DriverManager.Initialize: load failed: "
+ localException2);
}
}
以下是通過jdbc.drivers加載驅(qū)動driver的方式:
String url = "jdbc:oracle:thin:@127.0.0.1:1521:xe";
//設(shè)置值系統(tǒng)變量jdbc.drivers
System.setProperty("jdbc.drivers", "oracle.jdbc.driver.OracleDriver");
//2.通過特定的url獲取driver
Driver driver = DriverManager.getDriver(url);
//打印是否存在
System.out.println(driver);
請記住:一定要在第一次使用DriverManager之前設(shè)置jdbc.drivers,因為DriverManager中的static靜態(tài)代碼段只會被執(zhí)行一次!