浅析jdbc源码

jdk中规定了jdbc的相关接口,jdbc是spi(服务提供者接口)框架的良好应用.

类图如下:


首先上一段简单代码:

  public  void baseTest() throws SQLException, ClassNotFoundException {
        // 1.注册驱动
//        System.out.println(DriverManager.getLogWriter());
        DriverManager.setLogWriter(new PrintWriter(System.out));
//        DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//        System.setProperty("jdbc.drivers", "com.mysql.jdbc.Driver");
//        Class.forName("com.mysql.jdbc.Driver");// 推荐方式
        // 2.建立连接
        String url = "jdbc:mysql://127.0.0.1:3306/jdbc";
        String user = "root";
        String password = "123";
        Connection conn = DriverManager.getConnection(url, user, password);
        // 3.创建语句
        Statement st = conn.createStatement();
        // 4.执行语句
        ResultSet rs = st.executeQuery("select * from user");
        // 5.处理结果
        while (rs.next()) {
            System.out.println(rs.getObject(1) + "\t" + rs.getObject(2) + "\t"
                            + rs.getObject(3) + "\t" + rs.getObject(4));
        }
        // 6.释放资源
        rs.close();
        st.close();
        conn.close();
    }


我们简单分析 jdbk如何注册驱动和获取连接的过程

初始化时,loadInitializeDrivers()首先就要读取系统属性jdbc.drivers,如果设置了这个属性,那么就去加载属性描述的驱动类.完成后还利用ServiceLoader查找jar包META-INF/services/路径中的文件描述的实现了Driver接口的驱动类(服务提供者).

 

这个方法代码如下:

 

 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;
        }
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()

        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator driversIterator = loadedDrivers.iterator();

                /* Load these drivers, so that they can be instantiated.
                 * It may be the case that the driver class may not be there
                 * i.e. there may be a packaged driver with the service class
                 * as implementation of java.sql.Driver but the actual class
                 * may be missing. In that case a java.util.ServiceConfigurationError
                 * will be thrown at runtime by the VM trying to locate
                 * and load the service.
                 *
                 * Adding a try catch block to catch those runtime errors
                 * if driver not available in classpath but it's
                 * packaged as service and that service is there in classpath.
                 */
                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);
            }
        }
    }


这些驱动类在类初始化时一般调用了DriverManager.registerDriver()方法把自己注册上.所以我们就不要单独去注册了.

 

 


注意,如果jar中没有在META-INF/services/文件中描述驱动,那么一定需要自己注册.

 Class.forName("com.mysql.jdbc.Driver").这种方式会初始化类,从而注册驱动.

注册驱动的方法很简单:

   

 

public static synchronized void registerDriver(java.sql.Driver driver)
        throws SQLException {
        /* Register the driver if it has not already been added to our list */
        if(driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver));
        } else {
            // This is for compatibility with the original DriverManager
            throw new NullPointerException();
        }
        println("registerDriver: " + driver);
    }


获取连接代码:

 

 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());
            }
        }
        // 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");
    }


这个过程就是遍历已经注册的驱动,传入url(url中包含了使用的数据库协议),调用方法connect().如果返回为空,说明不是对应的数据库,继续尝试连接.
我们可以看看mysql是怎么使用url来获取connection的:

 

 

 

 public Connection connect(String url, Properties info) throws SQLException {
    if(url != null) {
      if(StringUtils.startsWithIgnoreCase(url, "jdbc:mysql:loadbalance://")) {
        return this.connectLoadBalanced(url, info);
      }
      if(StringUtils.startsWithIgnoreCase(url, "jdbc:mysql:replication://")) {
        return this.connectReplicationConnection(url, info);
      }
    }
    Properties props = null;
    if((props = this.parseURL(url, info)) == null) {
      return null;
    } else if(!"1".equals(props.getProperty("NUM_HOSTS"))) {
      return this.connectFailover(url, info);
    } else {
      try {
        com.mysql.jdbc.Connection ex = ConnectionImpl.getInstance(this.host(props), this.port(props), props, this.database(props), url);
        return ex;
      } catch (SQLException var6) {
        throw var6;
      } catch (Exception var7) {
        SQLException sqlEx = SQLError.createSQLException(Messages.getString("NonRegisteringDriver.17") + var7.toString() + Messages.getString("NonRegisteringDriver.18"), "08001", (ExceptionInterceptor)null);
        sqlEx.initCause(var7);
        throw sqlEx;
      }
    }
  }

 

 

 

 

 

如果url不正确,则返回null,否则返回具体的Connection

jdbc使用的设计模式:

(1)桥梁设计模式:

jdbc接口包含面向用户和数据库的就口,通过DriverManager和Driver桥接

Connection,Statement,ResultSet等接口面向用户,具体由数据库实现.

Driver是面向数据库,具体的Driver由数据库提供

jdbc的spi框架正式通过桥梁设计模式来实现的:

服务管理者:DriverManager

服务提供者接口:Driver

服务接口:Connection

(2)工厂方法设计模式:

具体数据库的Driver生成具体的Connection

posted @ 2019-01-23 21:09  hexel  阅读(961)  评论(0编辑  收藏  举报