JAVA标准库JDBC流程分析

2022-05-12 10:11:39 浏览数 (1)

结合debug的分析过程记录,一切结论都出于code。

为了方便分析,这里从h2的一个测试用例出发:

代码语言:javascript复制
/*
 * Copyright 2004-2020 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (https://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.samples;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import org.h2.tools.DeleteDbFiles;

/**
 * A very simple class that shows how to load the driver, create a database,
 * create a table, and insert some data.
 */
public class HelloWorld {

    /**
     * Called when ran from command line.
     *
     * @param args ignored
     */
    public static void main(String... args) throws Exception {
        // delete the database named 'test' in the user home directory
        DeleteDbFiles.execute("~", "test", true);

        Class.forName("org.h2.Driver");
        Connection conn = DriverManager.getConnection("jdbc:h2:~/test");
        Statement stat = conn.createStatement();

        // this line would initialize the database
        // from the SQL script file 'init.sql'
        // stat.execute("runscript from 'init.sql'");

        stat.execute("create table test(id int primary key, name varchar(255))");
        stat.execute("insert into test values(1, 'Hello')");
        ResultSet rs;
        rs = stat.executeQuery("select * from test");
        while (rs.next()) {
            System.out.println(rs.getString("name"));
        }
        stat.close();
        conn.close();
    }

}

1 注册加载驱动

1.1 一切从Class.forName说起

代码语言:javascript复制
...
  Class.forName("org.h2.Driver");
  Connection conn = DriverManager.getConnection("jdbc:h2:~/test");
  Statement stat = conn.createStatement();
...

这是一段比较标准的jdbc使用方法了,不论连接什么DB,class.forname总是需要显示配置,那么这里到底做了什么?

1.2 注册进DriverManager

Class.forName("org.h2.Driver");会自动load驱动类里面的静态方法,而所有实现java.sql.Driver的驱动,都要在静态方法内把自己注册DriverManager,例如h2的实现:

代码语言:javascript复制
public class Driver implements java.sql.Driver, JdbcDriverBackwardsCompat {

    private static final Driver INSTANCE = new Driver();
    private static final String DEFAULT_URL = "jdbc:default:connection";
    private static final ThreadLocal<Connection> DEFAULT_CONNECTION =
            new ThreadLocal<>();

    private static boolean registered;

    static {
        load();
    }
    ...
    ...
    public static synchronized Driver load() {
        try {
            if (!registered) {
                registered = true;
                DriverManager.registerDriver(INSTANCE);
            }
        } catch (SQLException e) {
            DbException.traceThrowable(e);
        }
        return INSTANCE;
    }
    ...
    ...
}

DriverManager的具体注册方法:

代码语言:javascript复制
    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);

    }

最终Driver信息注册进registeredDrivers

代码语言:javascript复制
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = 
	new CopyOnWriteArrayList<>();

1.3 getConnection

注册之后需要使用连接串使用驱动,拿到连接

"jdbc:h2:~/test"如何在registeredDrivers里面找到刚才注册的类?

代码语言:javascript复制
            Class.forName("org.h2.Driver");
-------->   Connection conn = DriverManager.getConnection("jdbc:h2:~/test");
            Statement stat = conn.createStatement();

调用栈

代码语言:javascript复制
acceptsURL:79, Driver
connect:55, Driver                  <----- 到H2
getConnection:664, DriverManager    <----- JDBC内
getConnection:270, DriverManager    <----- JDBC内
main:30, HelloWorld                 <----- 应用

看下是怎么从jdbc调进去的

代码语言:javascript复制
=======================================
getConnection:270, DriverManager    <----- JDBC内
=======================================
    @CallerSensitive
    public static Connection getConnection(String url) {
    ...
        return (getConnection(url, info, Reflection.getCallerClass()));
    ...
    }

=======================================
getConnection:664, DriverManager    <----- JDBC内

遍历刚刚的registeredDrivers
=======================================
	private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws {
    ...
    ...
        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());
            }

        }

getConnection:664, DriverManager 遍历注册的registeredDrivers,然后调如h2的代码内

代码语言:javascript复制
// jdbc:h2:~/test

    @Override
    public Connection connect(String url, Properties info) throws SQLException {
        try {
            if (info == null) {
                info = new Properties();
            }
            if (!acceptsURL(url)) {
                return null;
            }
            if (url.equals(DEFAULT_URL)) {
                return DEFAULT_CONNECTION.get();
            }
            return new JdbcConnection(url, info);
        } catch (Exception e) {
            throw DbException.toSQLException(e);
        }
    }

继续调如acceptsURL,返回true

代码语言:javascript复制
// jdbc:h2:~/test
    @Override
    public boolean acceptsURL(String url) {
        if (url != null) {
//     public static final String START_URL = "jdbc:h2:";
            if (url.startsWith(Constants.START_URL)) {
                return true;
            } else if (url.equals(DEFAULT_URL)) {
                return DEFAULT_CONNECTION.get() != null;
            }
        }
        return false;
    }

继续上面,new一个JdbcConnection出来

代码语言:javascript复制
// connect:64, Driver
    public Connection connect(String url, Properties info) throws SQLException {
...
            return new JdbcConnection(url, info);
...
}

// <init>:107, JdbcConnection
    public JdbcConnection(String url, Properties info) throws SQLException {
        this(new ConnectionInfo(url, info), true);
    }
代码语言:javascript复制
    public JdbcConnection(ConnectionInfo ci, boolean useBaseDir)
            throws SQLException {
    // h2 的内部调用,创建出一个server出来,然后把JdbcConnection返回去
    }

2 执行SQL

代码语言:javascript复制
			Class.forName("org.h2.Driver");
            Connection conn = DriverManager.getConnection("jdbc:h2:~/test");
            Statement stat = conn.createStatement();
            
----------> stat.execute("create table test(id int primary key, name varchar(255))");

conn已经是h2内部的JdbcConnection实现了,后续的调用直接调入h2的jdbc实现 (public class JdbcConnection implements Connection)

0 人点赞