##一下内容时笔者自己学习总结,谨慎参考##
呃,JDBC的设计框架
java作为一门但是到现在一直一门火热的语言使用者很多,所以java有能力去指定一套数据连接接口规范,而规范的具体实现交给数据库厂商去实现,也就是驱动是由数据库厂商提供的, 面对java庞大的开发者群体,数据库厂商必须得自己去实现接口规范,提供给开发者使用,这样自己的数据库才会被java开发者群体去使用。
所以大概类图就出来了:
那我们使用就只用这样就可以了:
Driver driver = new MysqlDriverImple();
Connecttion con = driver.getConnection(xxxx);
示例:
/**
* jdbc的使用顺序
* @Author: puhaiguo
* @Date: 2022-07-09 01:19
* @Version 1.0
*/
public class JdbcTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
/*实例化mysql的实现jdk Driver接口的类 Driver*/
Driver driver = new Driver();
Properties properties = new Properties();
properties.setProperty("user", "root");
properties.setProperty("password", "root");
Connection connect = driver.connect("jdbc:mysql://127.0.0.1:3306/", properties);
System.out.println(connect);
}
}
ennnn ,
那 JDBC的使用步骤?
绕开了DriverManager 驱动管理者,直接使用实现类
呃 设计模式的一个精髓就是面向抽象编程, 如果我们直接new 对象是不是,
以上的使用就会导致用户过渡依赖具体编程,
如果我们同时使用两个数据库连接驱动:
/**
* jdbc的使用顺序
* @Author: puhaiguo
* @Date: 2022-07-09 01:19
* @Version 1.0
*/
public class JdbcTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
/*实例化mysql的实现jdk Driver接口的类 Driver*/
Properties properties = new Properties();
properties.setProperty("user", "root");
properties.setProperty("password", "root");
/*mysql*/
Driver driverMySql = new Driver();
Connection connect = driverMySql.connect("jdbc:mysql://127.0.0.1:3306/", properties);
/*orcale*/
OracleDriver driverOrcale = new OracleDriver();
driverOrcale.connect("jdbc:oracle:thin:@//<host>:1521/service_name", properties );
}
}
这样用户我们使用者是不是与驱动绑定的关系有点密切,我们直接new的数据库驱动,
所以为了解耦, jdk又提供了一个DriverManager 来管理这些驱动, 我们开发者想要的就是我提供你连接地址、用户名、密码 直接给我一个连接对象就可以,这样我们是不是就与驱动具体的实现类解耦。 想法是好的,但是呃问题出现了。。。。
问题1、卧槽,DriverManager是jdk提供的东西,实现类是各各数据库厂商提供的, 我DriverManager要管理这些驱动,我首先得知道这些类的全路径类名,我才能加载这些类吧, 问题是不通数据库厂商的全路径类名是不一样,DriverManger如何才能知道? 提供一个静态方法,去注册 确实可行, 所以让数据厂商自己去注册驱动
示例 mysql:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
所以当我们只要加载类Driver这个类的时候静态代码块的方法就会执行,这样mysql的驱动就被注册到DriverMangerj🆗了, 我们只要DriverManager.getConnect()提供url ,用户名、密码。
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/", "root", "root");
getConnect的代码逻辑:
前置知识点:
@CallerSensitive + Reflection.getCallerClass()
在DriverManager的getConnection上大概的意思就是获取 是那个类里面调用了 DriverManger的getConnection方法。
代码逻辑:
@CallerSensitive
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);
}
// Reflection.getCallerClass() + @CallerSensitive 获取是哪个类调用了DriverManager这个方法
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; //获取caller的类加载器
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;
//refisteredDrivers 容器里面存有所有的注册驱动
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");
}
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
boolean result = false;
if(driver != null) {
Class<?> aClass = null;
try {
aClass = Class.forName(driver.getClass().getName(), true, classLoader);
} catch (Exception ex) {
result = false;
}
//前置知识: 一个类在jvm中的唯一性是由, 启动类加载+类的全路径类名 组成,所以如果同一个类路径被不同的类加载器加载,jvm中是存在两份不一样的class对象
result = ( aClass == driver.getClass() ) ? true : false;
}
return result;
}
问题二、这个驱动容器里面的驱动如何注册上去? 不需要手动注册,答案spi 服务发现机制
SPI服务发现机制
为什么我们可以省略Class.formName(“xxxx”), DriverManager的registeredDrivers 容器也可以获得所有的驱动类对象。
所以现在我们完全可以省略掉Class.formName, 直接获取连接就行:
/**
* jdbc的使用顺序
* @Author: puhaiguo
* @Date: 2022-07-09 01:19
* @Version 1.0
*/
public class JdbcTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/", "root", "root");
}
}
原理:
我们在使用DriverManager.get Connectiond的时候 首先会加载DriverManger类,接着进行静态代码块的执行。
public class DriverManager {
// List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
private static volatile int loginTimeout = 0;
private static volatile java.io.PrintWriter logWriter = null;
private static volatile java.io.PrintStream logStream = null;
// Used in println() to synchronize logWriter
private final static Object logSync = new Object();
/* Prevent the DriverManager class from being instantiated. */
private DriverManager(){}
/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
static {
loadInitialDrivers();//开始进行SPI入口
println("JDBC DriverManager initialized");
}
JDBC 体会
总结一下, DriverManager 的获取连接最终是调用Driver驱动具体的实现者,让用户感知我们是在和DriverManger的getConnection的接口打交道,但是实际调用实现者类是由DriverManager来做的,所以DriverManager可以说是用了 桥接模式 + SPI 服务发现机制,让我们开发者使用获取连接对象完美解耦与底层驱动实现。 我们的关注点不在是驱动而是面对驱动管理者。