前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JAVA标准库JDBC流程分析

JAVA标准库JDBC流程分析

作者头像
mingjie
发布2022-05-12 10:11:39
3470
发布2022-05-12 10:11:39
举报
文章被收录于专栏:Postgresql源码分析

结合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)

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-05-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 注册加载驱动
    • 1.1 一切从Class.forName说起
      • 1.2 注册进DriverManager
        • 1.3 getConnection
        • 2 执行SQL
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档