结合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的实现:
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
的具体注册方法:
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
里面找到刚才注册的类?
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)