JAVA数据库连接驱动设计的体会

##一下内容时笔者自己学习总结,谨慎参考##

呃,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 服务发现机制,让我们开发者使用获取连接对象完美解耦与底层驱动实现。 我们的关注点不在是驱动而是面对驱动管理者。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值