吃透MySQL(二):JDBC原理及源码解析


JDBC(Java DataBase Connectivity)就是Java数据库连接,说白了就是用Java语言来操作数据库。原来我们操作数据库是在控制台使用SQL语句来操作数据库,JDBC是用Java语言向数据库发送SQL语句。

一,JDBC使用

首先依赖mysql驱动jar包:maven地址

	public static void main(String[] args) throws Exception {
    
    
        // 加载驱动
        Class.forName("com.mysql.jdbc.Driver");
        // 获取MySQL连接
        Connection connection = DriverManager.getConnection("jdbc:mysql://@localhost:3306/bobo,"root","密码");
        // 创建SQL执行器
        Statement statement = connection.createStatement();
        // 执行查询语句
        ResultSet resultSet = statement.executeQuery("select * from user");
        // 解析结果
        while (resultSet.next()){
    
    
            System.out.println(resultSet.getString("name"));
        }
        // 关闭连接
        statement.close();
        connection.close();
    }

注意:从mysql6开始,驱动的路径有更改,而且URL中要加入时区:

	// 加载驱动
    Class.forName("com.mysql.cj.jdbc.Driver");
    // 获取MySQL连接
    Connection connection = DriverManager.getConnection("jdbc:mysql://@localhost:3306/bobo?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false","root","密码");

二,JDBC原理

1,JDBC基本原理

早期SUN公司的天才们想编写一套可以连接天下所有数据库的API,但是当他们刚刚开始时就发现这是不可完成的任务,因为各个厂商的数据库服务器差异太大了。

后来SUN开始与数据库厂商们讨论,最终得出的结论是,由SUN提供一套访问数据库的规范(就是一组接口),并提供连接数据库的协议标准,然后各个数据库厂商会遵循SUN的规范提供一套访问自己公司的数据库服务器的API出现。SUN提供的规范命名为JDBC,而各个厂商提供的,遵循了JDBC规范的,可以访问自己数据库的API被称之为驱动!

在这里插入图片描述

JDBC是接口,而JDBC驱动才是接口的实现,没有驱动无法完成数据库连接!每个数据库厂商都有自己的驱动,用来连接自己公司的数据库。

JDBC中定义了一些接口:

  • 驱动管理:DriverManager
  • 连接接口:Connection,DatabasemetaData
  • 语句对象接口:Statement,PreparedStatement,CallableStatement
  • 结果集接口:ResultSet,ResultSetMetaData。

上面介绍了JDK中定义了JDBC接口规范,实现交给各个数据库厂商,那么JDK是怎么发现厂商提供的驱动呢?那么就要用到Java SPI机制了。

2,Java SPI机制

SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制, 
比如有个接口,想运行时动态的给它添加实现,你只需要添加一个实现。我们经常遇到的就是java.sql.Driver接口,
其他不同厂商可以针对同一接口做出不同的实现,mysql和postgresql都有不同的实现提供给用户,
而Java的SPI机制可以为某个接口寻找服务实现。

在这里插入图片描述

如上图所示,接口对应的抽象SPI接口;实现方实现SPI接口;调用方依赖SPI接口。

SPI接口的定义在调用方,在概念上更依赖调用方;组织上位于调用方所在的包中,实现位于独立的包中。

当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务实现的工具类是:java.util.ServiceLoader。

三,JDBC源码分析

DriverManager是jdbc里管理和注册不同数据库driver的工具类。针对一个数据库,可能会存在着不同的数据库驱动实现。我们在使用特定的驱动实现时,不希望修改现有的代码,而希望通过一个简单的配置就可以达到效果。 在使用mysql驱动的时候,会有一个疑问,DriverManager是怎么获得某确定驱动类的?我们在运用Class.forName(“com.mysql.jdbc.Driver”)加载mysql驱动后,就会执行其中的静态代码把driver注册到DriverManager中,以便后续的使用。

Class.forName("com.mysql.cj.jdbc.Driver");

利用反射来加载mysql驱动包中的Driver类,然后会促发Driver类的静态代码块的执行:

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

    /**
     * Construct a new driver and register it with DriverManager
     * 
     * @throws SQLException
     *             if a database error occurs.
     */
    public Driver() throws SQLException {
    
    
        // Required for Class.forName().newInstance()
    }
}

Driver中的静态代码块,把自己注册到JDKDriverManager中,以供后面使用。

public class DriverManager {
    
    
    
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    
    public static synchronized void registerDriver(java.sql.Driver driver)
        throws SQLException {
    
    

        registerDriver(driver, null);
    }
    
    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);

    }
}

将自己注册到驱动管理器的驱动列表中.

当获取连接的时候调用驱动管理器的连接方法从列表中获取:

	public static Connection getConnection(String url,
        String user, String password) throws SQLException {
    
    
        java.util.Properties info = new java.util.Properties();

        if (user != null) {
    
    
            info.put("user", user);
        }
        if (password != null) {
    
    
            info.put("password", password);
        }

        return (getConnection(url, info, Reflection.getCallerClass()));
    }

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

DriverManager.getCollection()方法会遍历已经注册到系统中的驱动,调用驱动中相应的方法来得到真正的数据库连接。

自JDBC4.0开始,Class.forName(""),可以省略掉了

注释掉Class.forName("com.mysql.jdbc.Driver"),直接去获取连接:

 // 加载驱动,这步可以省略不写
 // Class.forName("com.mysql.jdbc.Driver");
 // 获取MySQL连接
 Connection connection = DriverManager.getConnection("jdbc:mysql://@localhost:3306/bobo","root","zhao635481603");

当我们调用DriverManager.getConnection()的时候,会促发DriverManager的静态代码块执行:

public class DriverManager {
    
    

    /**
     * Load the initial JDBC drivers by checking the System property
     * jdbc.properties and then use the {@code ServiceLoader} mechanism
     */
    static {
    
    
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
}

可以看到其内部的静态代码块中有一个loadInitialDrivers方法,loadInitialDrivers用法用到了上文提到的spi工具类ServiceLoader:

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<Driver> 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的静态代码块儿里会寻找 jdbc.drivers 这个系统变量,找到相应的驱动程序并使用Class.forName()来加载它,相当于我们之前写的Class.forName("com.mysql.jdbc.Driver");

猜你喜欢

转载自blog.csdn.net/u013277209/article/details/113242740
今日推荐