前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你真的的懂JDBC?

你真的的懂JDBC?

作者头像
加多
发布2018-09-06 15:03:57
5410
发布2018-09-06 15:03:57
举报
文章被收录于专栏:Java编程技术Java编程技术

一、前言

Java中操作数据库元老是使用JDBC,而JDBC内部是如何实现的,为何每次使用时候都是写那些不理解的几行固定代码?这些看似不相关的代码内部是否有瓜葛那,下面进来探讨一二。

二、一个例子

public class TestJdbc {

    public static final String url = "jdbc:mysql://127.0.0.1/users";
    public static final String name = "com.mysql.jdbc.Driver";
    public static final String user = "root";
    public static final String password = "123456";
    public static final String sql = "select * from user";

    public static void main(String[] args) throws UnexpectedInputException, ParseException, Exception {
        Connection conn = null;
        PreparedStatement pst = null;
        ResultSet rs = null;
        try {
            // (1)注册驱动
            Class.forName(name);

            //(2) 获取链接
            conn = DriverManager.getConnection(url, user, password);

            // (3)准备语句
            pst = conn.prepareStatement(sql);

            // (4)执行查询
            rs = pst.executeQuery();

            // (5)迭代结果
            while (rs.next()) {
                System.out.println(rs.getString("username"));
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {

                if (rs != null) {
                    rs.close();
                }

                if (null == pst) {
                    pst.close();

                }

                if (null != conn) {
                    conn.close();

                }

            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}

使用JDBC我们都知道按部就班的写(1)(2)的代码获取数据库链接,但是很少去研究这两个语句是干啥用的,特别是第一句,直接使用类加载器加载了驱动类到内存,这是何意?

三、原理

3.1 注册驱动

其实Class.forName(name);的作用是注册mysql驱动到驱动管理器,只有注册后,在(2)获取链接时候才能获取到mysql的数据库链接。Class.forName作用是加载类的字节码到内存生成Class对象,那么这里就是把类com.mysql.jdbc.Driver字节码加载到内存生成com.mysql.jdbc.Driver的Class对象,有了Calss对象就可以使用new了,或者Class.newInstance()了创建对象实例了。下面看看com.mysql.jdbc.Driver代码:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // 注册mysql驱动本身到驱动管理器
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

在创建Driver的Class对象是会调用static块,注册当前驱动到驱动管理器。下面看看驱动管理器registerDriver方法:

public static synchronized void registerDriver(java.sql.Driver driver)
        throws SQLException {

        registerDriver(driver, null);
}

 public static synchronized void registerDriver(java.sql.Driver driver,
            DriverAction da)
        throws SQLException {

        //注册驱动到并发安全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);

  }

其中registeredDrivers是个CopyOnWriteArrayList,是线程安全的list.

    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

一个问题:这里我们的main函数是使用AppClassLoader加载的,而DriverManager类则属于rt.jar(使用bootStarp加载),而调用类与被调用类使用的应该是同一个类加载器,那这里main函数为啥能调用DriverManager那,其实是因为Java的类加载委托机制。

3.2 获取连接

DriverManager类是驱动管理器类,里面也有个static块:

public class DriverManager {
    ...

    /**
     * Load the initial JDBC drivers by checking the System property
     * jdbc.properties and then use the {@code ServiceLoader} mechanism
     */
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
}
private static void loadInitialDrivers() {

        //获取jdbc.drivers属性里面的驱动名称
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        ...
        //使用SPI技术加载所有实现了java.sql.Driver的驱动类
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                ...
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

        println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers == null || drivers.equals("")) {
            return;
        }

        //加载所有jdbc.drivers属性里面的驱动名称
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

关于SPI 参考:http://www.jianshu.com/p/a4dc755652ff 第三节

其实由于static块已经加载了所有驱动,(1)看似是可有可无的

(2)中从驱动管理器里面获取数据库连接,下面看下里面做了啥:

private static Connection getConnection(
    String url, java.util.Properties info, Class<?> caller) throws SQLException {
   
    //(1)选择类加载器
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
        //如果类加载器为null,则使用线程上下文加载器
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }

    if(url == null) {
        throw new SQLException("The url cannot be null", "08001");
    }
    ...
    SQLException reason = null;
    
    //(2)遍历所有注册的驱动,找打一个可用的发起连接并返回
    for(DriverInfo aDriver : registeredDrivers) {
        //如果有权限则获取驱动并获取连接
        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());
        }

    }

    // (3)到这里如果还没有获取连接,则返回失败原因
    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");
}
  • (1)设置类加载器,这里因为caller为TestJdbc.class,所以callerCL为AppClassLoader, 这里如果callerCL为null,则使用线程上下文加载器,关于线程上下文加载器参考 http://www.jianshu.com/p/a4dc755652ff 的第三节。这给应用使用rt.jar包外的路径加载JDBC驱动提供了途径。
  • (2)遍历注册的所有驱动,找到一个使用callerCL加载器能加载成功的驱动,获取连接。

其中鉴权代码为:

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;
        }

         result = ( aClass == driver.getClass() ) ? true : false;
    }

    return result;
}

使用callerCL类加载器去加载driver,然后判断当前加载的Class和driver的Class是否是同一个对象,是则成功,否者失败。这里注册Driver时候类加载器为BootStrap,而callerCL是AppClassloader,由于委托机制,所以这里返回true.也就是注册驱动时候类加载器和使用时候必须是同一个或者具有委托关系时候才会鉴权成功,也就是说才有权限去调用驱动的connect方法。

另外比如我们调用:

Class.forName("oracle.jdbc.driver.OracleDriver");

Class.forName("com.mysql.jdbc.Driver");

注册了两个驱动到驱动管理器,那么当调用DriverManager.getConnection(url, user, password);传递mysql数据库的url,user,password时候,假如第一次返回的是oracle的驱动,则调用connect时候会返回SQLException异常,然后循环获取注册的下一个驱动,返回mysql驱动后则connect 成功返回数据库链接。

  • (3)如果没有可用的驱动则返回错误信息。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
  • 二、一个例子
  • 三、原理
  • 3.1 注册驱动
    • 3.2 获取连接
    相关产品与服务
    云数据库 SQL Server
    腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档