1. 程式人生 > >JDBC解惑(一) 之 Connection建立

JDBC解惑(一) 之 Connection建立

問題:

<span style="font-family:Courier New;">最近接觸到jdbc,我有個疑問,建立連線的時候呼叫的都是介面,那麼介面的方法是怎麼實現的呢? 
比如宣告: 
Connection con;  
Statement sql; 
ResultSet rs;  //Connection 、Statement 、ResultSet 都是介面 

con=DriverManage.getConnection("jdbc:odbc:hello”,"",""); // DriverManage是一個類,getConnection是靜 態方法,返回型別是Connection介面; 
sql=con.createStatement(); 
rs=sql.executeQuery(“SELECT *FROM table”);  


我想問一下,Statement介面的getConnection,ResultSet介面的executeQuery方法都沒有看到實現,怎麼就直接用了。</span>
引自:http://bbs.csdn.net/topics/391950254

一份Mysql連線資料庫的簡單程式碼,就是查詢輸出一下123:

<span style="font-family:Courier New;">package csdn.question;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;



public class ConnectionDB {
	private Connection conn=null;
	private PreparedStatement statement=null;
	private ResultSet rs=null;
	@Before
	public void getMysqlConnection() throws ClassNotFoundException, SQLException{
		Class.forName(com.mysql.jdbc.Driver.class.getName());
		conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "dev", "dev");
	}
	
	@Test
	public void test() throws SQLException{
			statement= conn.prepareStatement("select 123");
			rs = statement.executeQuery();
			while(rs.next()){
				System.out.println(rs.getInt(1));
			}
	}
	@After
	public void closeConnection(){
		if(rs!=null){
			try {
				rs.close();
			} catch (Exception e) {
			}
		}
		if(statement!=null){
			try {
				statement.close();
			} catch (Exception e) {
			}
		}
		if(conn!=null){
			try {
				conn.close();
			} catch (Exception e) {
			}
		}
		if(conn!=null){
			try {
				conn.close();
			} catch (Exception e) {
			}
		}
	}
}

</span>

和其他連線資料的程式碼一樣,開始先註冊Mysql Driver:

Class.forName("com.mysql.jdbc.Driver");

為什麼要註冊Driver,Driver是什麼東西?

先看java.sql.Driver類,所有driver 類必須實現的介面


載入com.mysql.jdbc.Driver這個類做了什麼?

com.mysql.jdbc.Driver原始碼:

<span style="font-family:Courier New;">public class Driver extends NonRegisteringDriver implements java.sql.Driver {

	public Driver() throws SQLException {
	}

	static {
		try {
			DriverManager.registerDriver(new Driver());
		} catch (SQLException E) {
			throw new RuntimeException("Can't register driver!");
		}
	}
}</span>

可以看出來com.mysql.jdbc.Driver把自己的一個物件註冊給了java.sql.DriverManager

然後看一下java.sql.DriverManager.getConnection(String, Properties, Class<?>)這個方法,所有的getConnection方法最後都會呼叫這個方法

<span style="font-family:Courier New;">//  Worker method called by the public getConnection() methods.
    private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        /*
         * When callerCl is null, we should check the application's
         * (which is invoking this class indirectly)
         * classloader, so that the JDBC driver class outside rt.jar
         * can be loaded from here.
         */
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized (DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }

        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }

        println("DriverManager.getConnection(\"" + url + "\")");

        // Walk through the loaded registeredDrivers attempting to make a connection.
        // Remember the first exception that gets raised so we can reraise it.
        SQLException reason = null;

        for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }</span>
<span style="font-family:Courier New;"> // List of registered JDBC drivers
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();</span>
使用DriverManager獲取Connection的時候,DriverManager會遍歷他儲存的driver物件用url, info引數嘗試去建立連線,如果連線能夠建立那麼就會返回這個連線,如果沒有合適的driver物件獲取連線那麼會丟擲SQLException
<span style="font-family:Courier New;">throw new SQLException("No suitable driver found for "+ url, "08001");</span>

結論:

Conenction的獲取並不是平白無故從石頭縫裡面蹦出來的,而是使用自己載入的Driver類的物件使用connect方法建立的。

程式碼稍微變化一下我們看看DriverManager類中有幾種驅動?

<span style="font-family:Courier New;">@Before
	public void getMysqlConnection() throws ClassNotFoundException, SQLException{
		Class.forName("com.mysql.jdbc.Driver");
		conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "dev", "dev");
		
		Enumeration<Driver> drivers = DriverManager.getDrivers();
		while(drivers.hasMoreElements()){
			Driver driver=drivers.nextElement();
			System.out.println(driver);
		}
	}</span>
結果:
<span style="font-family:Courier New;">[email protected]
[email protected]
[email protected]
123</span>
什麼鬼?
我只註冊了一個怎麼有三個驅動?

這就要說一說Java靜態語句塊,靜態語句塊在類載入的時候會被執行,且只執行一次。

因為我們把mysql驅動包加到classpath下面的時候(也就是myeclipse的build path下)類會自動載入到JVM中,所以在static語句塊中的註冊程式碼被自動註冊進DriverManager,其中
com.mysql.jdbc.Driver和com.mysql.fabric.jdbc.FabricMySQLDriver是Mysql驅動包提供的而sun.jdbc.odbc.JdbcOdbcDriver是JDK自帶的

所以,以後寫JDBC程式碼不要再寫

Class.forName("com.mysql.jdbc.Driver");

一是沒必要,二還顯得我們low。

樓主當年也是被網路上的demo程式碼坑的死死的!

今天又回來重新編輯這篇部落格,是因為紅色標記的這部分描述是錯誤的

下面我們做個測試:

Manager類

package staticImport;
import java.util.LinkedList;
import java.util.List;

public abstract class Manager {
	private static List<String> items = new LinkedList<String>();

	public static void regeister(String item) {
		items.add(item);
	}
	public static void say(){
		System.out.println(items.size());
		for(String item:items){
			System.out.println(item);
		}
	}
}

程式碼很簡單,一個類變數,用於儲存註冊的資訊,兩個類方法一個用來註冊資訊,一個方法用來輸出註冊過的資訊。

Item1類

package staticImport;

public class Item1 {
	static {
		Manager.regeister("Item1");
	}
}
也很簡單,只是在靜態塊裡面執行一下資訊註冊。

我們接著看場景類

package staticImport;

public class Client {
	public static void main(String[] args) {
		Manager.say();
	}
}
執行一下,我們看結果:
0

結果顯示0,0條註冊資訊。What?說明並沒有自動註冊。那是怎麼回事?

然後我們更改場景類:

package staticImport;

public class Client {
	public static void main(String[] args) throws ClassNotFoundException {
		<span style="color:#ff0000;">Class.forName("staticImport.Item1",false,Thread.currentThread().getContextClassLoader());</span>
		Manager.say();
	}
}

結果依然是0,然後把紅色程式碼中的false改為true,下面的結果可能就是你之前的預期:
1
Item1
看來這個結果還類的初始化有關,那麼我們看java的語言規範

12.4中提到,

Initialization of a class consists of executing its static initializers and the initializers for static fields (class variables) declared in the class.

Initialization of an interface consists of executing the initializers for fields (constants) declared in the interface.

看來確實和類的初始化有關,那麼類什麼時候初始化呢?

接著看:

  • T is a class and an instance of T is created.(一個類的物件例項被建立)

  • static method declared by T is invoked.(一個靜態方法被呼叫)

  • static field declared by T is assigned.(一個靜態屬性被賦值)

  • static field declared by T is used and the field is not a constant variable.(靜態屬性被使用並且這個屬性不是常量)

  • T is a top level class  and an assert statement lexically nested within T is executed.(說的是繼承的情況)

以上情況會觸發類的初始化。 但是為什麼之前的資料庫驅動被載入了呢? 這是JDBC4.0的新特性之一。 JDBC 4.0中增加的主要特性包括:
1. JDBC驅動類的自動載入
2. 連線管理的增強
3. 對RowId SQL型別的支援
4. SQL的DataSet實現使用了Annotations
5. SQL異常處理的增強
6. 對SQL XML的支援 

江山代有才人出,各領風騷數百年!