初探JDBC源码
aCoder2013 opened this issue · 0 comments
JDBC Driver注册
JDBC的核心接口之一是java.sql.Driver
,每一个驱动都必须提供实现类,那么它是怎样和DriverManager一起为我们提供数据库连接服务的呢,首先看一段经典的连接JDBC的代码.
//Class.forName("com.mysql.jdbc.Driver") JDBC4不加这行代码也可以
Connection con = DriverManager.getConnection(
"jdbc:mysql:///test",
username,
password);
那么这段代码究竟做了什么神奇的操作呢,我们直接进去DriverManager
这个类一看究竟,可以看到构造器是私有的,从而阻止我们去初始化这个类,JVM加载这个类之后首先会调用它的static
代码段,
DriverManager的static段调用了loadInitialDrivers()方法。
private DriverManager(){}
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
String drivers;
//第一部分
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
//第二部分
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
//第三部分
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
第一部分
这段代码首先会去获取jdbc.drivers这个系统属性,可以通过
-Djdbc.drivers=com.mysql.jdbc.Driver
或者在代码中显示设置
System.setProperty("jdbc.drivers","com.mysql.jdbc.Driver");
然后会通过ServiceLoader加载驱动程序,在JDBC4中,驱动程序必须在META-INF/services/
包含java.sql.Driver
这个文件,在其中包含数据库驱动的实现类,
比如Mysql的中包含的内容是:
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
第二部分
然后会加载找到的所有驱动程序,这个时候会调用驱动程序的static代码块,我们去看一下Mysql和H2数据库的Driver实现类在其中做了什么操作:
public class com.mysql.jdbc.Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
public class org.h2.Driver implements java.sql.Driver {
private static final Driver INSTANCE = new Driver();
static {
load();
}
public static synchronized Driver load() {
try {
if (!registered) {
registered = true;
DriverManager.registerDriver(INSTANCE);
}
} catch (SQLException e) {
DbException.traceThrowable(e);
}
return INSTANCE;
}
可以看到其中的共同点是都调用了DriverManager.registerDriver()去注册自己,其做的操作是将Driver封装成DriverInfo放到一个列表中保存。
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
registerDriver(driver, null);
}
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
/* Register the driver if it has not already been added to our list */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
第三部分
如果判断drivers为空,说明没有通过jdbc.drivers找到驱动类,接着有没有通过ServiceLoader找到驱动类并初始化都无所谓了,直接返回即可,如果drivers不为空,则通过Class.forName()去加载驱动类,
调用static代码块去注册自己,同时通过String[] driversList = drivers.split(":");
可以看出,指定jdbc.drivers
指定驱动时可以给出多个,用:
分隔。
获取连接
经过上面这段代码,DriverManager已经初始化完毕,各个驱动也已经注册完成,接着就调用getConnection()去获取连接,其核心代码也很容易理解,就是调用connect()方法去建立连接
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());
}
}