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通过桥接模式将接口已经规范好了,而具体的实现则由具体数据库决定。