JDBC操作数据库时我们第一步是调用Class.forName注册数据库驱动
public class Test {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","rootroot");
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("select * from user where id = 1");
while (resultSet.next()){
String name = resultSet.getString("name");
System.out.println(name);
}
}
}
为什么调用Class.forName就能够把驱动加载进来呢?第一反应是看forName方法做了什么操作
/**
* Returns the {@code Class} object associated with the class or
* interface with the given string name. Invoking this method is
* equivalent to:
*
* <blockquote>
* {@code Class.forName(className, true, currentLoader)}
* </blockquote>
*
*返回与给定字符串名的类或接口相关联的{@code Class}对象。调用这个方法相当于:Class.forName(className, true, currentLoader)
*/
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
从注释可以得出forName(String className)是会初始化类的,也就是说类中的静态变量和静态代码块会被执行,所以com.mysql.jdbc.Driver类将会被加载并初始化
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
// 这里把自己注册了
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
查看Driver源码也证实了这点,就是在类被加载时在静态代码块初始化了Driver对象。DriverManager.registerDriver(new Driver) 等价于Class.forName("com.mysql.jdbc.Driver")。到此我们便清楚的知道了驱动类是怎么被加载的。
接下来我们再看DriverManager.registerDriver方法,看看究竟数据库驱动是怎么被注册和使用的
// List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
/**
* Registers the given driver with the {@code DriverManager}.
* A newly-loaded driver class should call
* the method {@code registerDriver} to make itself
* known to the {@code DriverManager}. If the driver is currently
* registered, no action is taken.
*
* @param driver the new JDBC Driver that is to be registered with the
* {@code DriverManager}
* @param da the {@code DriverAction} implementation to be used when
* {@code DriverManager#deregisterDriver} is called
* @exception SQLException if a database access error occurs
* @exception NullPointerException if {@code driver} is null
* @since 1.8
*/
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);
}
从源码里我们可以清晰的看到被new的driver对象被加到了一个list中。注册好驱动下一步则是获取连接Connection
// 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());
// 这里利用注册进来的driver对象创建了连接对象
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());
}
}
// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
实际上获取连的是被注册进去的com.mysql.jdbc.Driver创建的。分析这几个类可以看出JDBC使用了桥接模式来达到接口与具体实现的分离。所以我们能够随意的变化底层数据库,但是jdbc的使用方法不变,唯一变的仅仅是注册时的驱动而已,jdbc通过桥接模式将接口已经规范好了,而具体的实现则由具体数据库决定。