• jdbc如何注册数据库驱动Driver的?


    1. 先看看原生jdbc执行sql的步骤

    // 在程序启动的时候需要注册一次mysql驱动,必须引入 mysql-connnector-java 的包
    Class.forName("com.mysql.jdbc.Driver");
    
    // 创建数据库连接
    Connection connection = DriverManager.getConnection("jdbcUrl", "userName", "password");
    // 创建执行语句
    Statement statment = connection.createStatement();
    // 执行sql,返回结果集
    ResultSet resultSet =  statement.executeQuery("select * from table;");
    // 最后,关闭连接
    connection.close();
    

    2. 为啥 Class.forName("com.mysql.jdbc.Driver") 加载一下类就能注册驱动了?

    看看 com.mysql.jdbc.Driver 类的代码 (版本5.1.47)

    public class Driver  extends NonRegisteringDriver implements java.sql.Driver {
        //
        // Register ourselves with the DriverManager
        //
        static {
            try {
                java.sql.DriverManager.registerDriver(new Driver());
            } catch (SQLException E) {
                throw new RuntimeException("Can't register driver!");
            }
        }
    

    当类加载的时候会执行静态代码块,从上面可以看到,mysql的Driver静态代码块里边 将自己注册到 DriverManager 里了; 所以后续的 DriverManager#getConnection 的过程能使用到mysql的driver

    3. DriverManager 在上述jdbc执行过程中的作用

    3.1 DriverManager.registerDriver 显式/主动注册驱动

    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();  //驱动列表
     
    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);
        }
    

    registeredDrivers.addIfAbsent(new DriverInfo(driver, da)); 驱动的注册过程就是将其加入到 一个 copyOnWrite 的列表里;

    3.2 DriverManager#getConnection 连接创建过程

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

    4. DriverManager 自动加载驱动

    除了主动调用 DriverManager#registerDriver() 注册驱动外,DriverManager 其实还有两种自动加载驱动的机制:

    • 系统变量 jdbc.drivers 定义的驱动, 然后内部也是通过 Class.forName 去注册这些驱动类
    • 基于 ServiceLoader 的 service-provicer SPI 机制, 即数据库驱动提供方在其类路径下存在文件 /META-INF/services/java.sql.Driver 指定其驱动的实现类,就能被DriverManager自动加载到;
      比如
      mysql-connector-java

    具体逻辑: 在 DriverManager 的静态代码块里,会执行 loadInitialDrivers() 一个自动驱动加载逻辑

     private static void loadInitialDrivers() {
            String drivers;
            try {
    			//1. 加载系统变量 jdbc.drivers 设置的冒号隔开的驱动
                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() {
    	
    				// 2. service-provider 加载机制
                    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 去加载 jdbc.drivers 定义的Driver类名称
                    Class.forName(aDriver, true,
                            ClassLoader.getSystemClassLoader());
                } catch (Exception ex) {
                    println("DriverManager.Initialize: load failed: " + ex);
                }
            }
        }
    

    总结

    System.getProperty('jdbc.drivers") 这种方式个人觉得适合用于在应用初始化脚本中指定

    基于 ServiceLoader 的SPI方式,只要符合 spi 标准的驱动包引入了就会自动加载
    我们在基于jdbc实现或封装数据库连接池、中间件的时候,就不需要再 Class.forName 的方式去显示注册驱动了,特别是当需要支持多种数据库的时候,还得根据 jdbcUrl 去判断目标 driverClassName 是啥, 如果要再支持新的关系型数据库时候 还得再改代码

    看看常用关系数据库厂商的 spi 支持情况

    • mysql-connector-java 早在5.0.0(2005-12-22) 版本就添加了 META-INF/services/java.sql.Driver 文件支持 service-provicer SPI; 现在一般用8.x的版本
    • pg connector 也在Version 42.2.13(2020-06-04) 之后支持

    所以如果不用支持古董版本的驱动,基本可以放心直接 DriverManager#getConnection 去创建db连接了

    本文来自博客园,作者:mushishi,转载请注明原文链接:https://www.cnblogs.com/mushishi/p/15143255.html

  • 相关阅读:
    怎样提高js的编程能力
    如何提升自己
    利用nginx做反向代理解决前端跨域问题
    vue项目中使用组件化开发
    vue中refs的使用
    vue项目使用keep-alive的作用
    hadoop安装、使用过程的异常汇总
    CSS选择器优先级总结
    为什么CSS选择器是从右往左解析
    MySql中游标的定义与使用方式
  • 原文地址:https://www.cnblogs.com/mushishi/p/15143255.html
Copyright © 2020-2023  润新知