• 浅析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) + "	" + rs.getObject(2) + "	"
                                + rs.getObject(3) + "	" + 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

  • 相关阅读:
    url路由配置及渲染方式
    django类视图介绍与类视图装饰器
    什么是数据类型
    python代码的编写和运行
    python环境搭建
    python教程(目录)
    编程语言概念
    面向对象入门
    编程语言
    Tornado框架实现图形验证码功能
  • 原文地址:https://www.cnblogs.com/xingxingge/p/10311456.html
Copyright © 2020-2023  润新知