java jdk8 源码解析之sql包:JDBC源码解析

在开发项目时我们经常会需要与数据库进行交互,为了统一标准,在java jdk中提供了一组与数据库交互的api(java.sql.*),每个厂商通过继承实现sql包下的接口和类完成与数据库交互的工作。以mysql为例:

public static void main(String[] arg) throws Exception {


    Class.forName("com.mysql.jdbc.Driver");
    Connection connection= DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","123456");
    PreparedStatement prepareStatement=connection.prepareStatement("select * from student");
    ResultSet resultSet=prepareStatement.executeQuery();
    while(resultSet.next()){
      System.out.println(resultSet.getString("id")+":"+resultSet.getString("studname"));
    }
    connection.close();
}

主要涉及到的类有:

  • connection:接口类,mysql封装了连接数据库的参数,辅助类,提供了sql语句执行,创建statement对象,提交,回滚等功能。
  • preparedStatement:接口类,保存sql执行语句,并提供查询,修改等方法。
  • Driver:驱动类,子类提供了返回connection对象方法的实现,以及一些辅助方法
  • DriverManager:驱动管理类,注册Driver对象

下面我们来一步步解析查询数据库的过程

1.驱动类加载

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

当我们看到这行代码时我们可能会有些疑惑:为什么开始要加载初始化这个驱动?那我们先看看它里面有什么。

//Driver 类
static {
   try {
      java.sql.DriverManager.registerDriver(new Driver()); //注册驱动
   } catch (SQLException E) {
      throw new RuntimeException("Can't register driver!");
   }
}
 //DriverManager 类
 private final static CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList<>();
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);

}

看到这里我们知道初始化类是为了调用DriverManager.registerDriver 方法对Driver进行注册。在注册方法中将获取到的Driver对象封装了一遍存入registeredDrivers集合里,这里registeredDrivers是DirverManager里的一个list集合对象,CopyOnWriteArrayList是一个线程安全list集合类。所以jdbc可以允许我们在同一项目中加载不同的驱动类去连接多个的数据库。

Driver类加载完成之后接下来是

Connection connection= DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","123456");

这一段是返回connection对象的操作,我们看一下DriverManager内源码,其中有一段

static {
    loadInitialDrivers();  /加载初始化其他驱动类
    println("JDBC DriverManager initialized");
}

private static void loadInitialDrivers() {
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction() {  //跳过权限验证从系统变量中获取驱动
            public String run() {
                return System.getProperty("jdbc.drivers"); //获取jdbc.drivers变量
            }
        });
    } catch (Exception ex) {
        drivers = null;
    }
    // If the driver is packaged as a Service Provider, load it.
    // Get all the drivers through the classloader
    // exposed as a java.sql.Driver.class service.
    // ServiceLoader.load() replaces the sun.misc.Providers()

    AccessController.doPrivileged(new PrivilegedAction() {
        public Void run() {

            ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);//通过ServiceLoader动态加载驱动类
            Iterator driversIterator = loadedDrivers.iterator();

            /* Load these drivers, so that they can be instantiated.
             * It may be the case that the driver class may not be there
             * i.e. there may be a packaged driver with the service class
             * as implementation of java.sql.Driver but the actual class
             * may be missing. In that case a java.util.ServiceConfigurationError
             * will be thrown at runtime by the VM trying to locate
             * and load the service.
             *
             * Adding a try catch block to catch those runtime errors
             * if driver not available in classpath but it's
             * packaged as service and that service is there in classpath.
             */
            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;
    }
    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);
        }
    }
}

在DriverMnager中有一段静态代码块,我们第一次调用时会执行里面的loadInitialDrivers方法完成第二次驱动的加载,这里加载驱动的方式有两种。一种是通过获取系统的环境变量jdbc.drivers得到驱动类的完全限定名并通过反射进行初始化注册,另一个是通过serviceLoader(接下来我们会讲解)动态获取驱动类对象完成注册,两种方式都在AccessController.doPrivileged内执行,是为了跳过虚拟机权限验证。

serviceLoader
serviceLoader类实现原理类似于我们所熟悉的工厂模式,在一般的工厂模式中我们不需要关系工厂类中具体的实现过程,只需要通过调用对象创建方法传递不同值的参数来获取不同的对象。在ServiceLoad类的实现中我们只需要在项目的META-INF/services/目录下配置需要加载的几个类的完全限定名后通过load方法就可以返回想要的对象。具体操作如下。

首先我们先创建需要加载的接口类以及他的实现类:

public interface HelloService {

   public void hello();
}

public class manService implements HelloService {

   @Override
   public void hello() {
      System.out.println("im a man");
   }
}

然后在META-INF下创建一个以HelloService类全名为名字的文件

java jdk8 源码解析之sql包:JDBC源码解析_第1张图片这个文件的内容里填写上需要加载的类 如下:

java jdk8 源码解析之sql包:JDBC源码解析_第2张图片
配置完成后我们执行的代码如下:

public static void main(String[] arg) throws Exception {

   ServiceLoader loadedDrivers = ServiceLoader.load(HelloService.class);
   Iterator driversIterator = loadedDrivers.iterator();
   while(driversIterator.hasNext()) {
      HelloService hello=driversIterator.next();
       hello.hello();
   }
}

这样我们就可以通过配置文件的方式加载我们需要的类。除此之外他还有其他重载的方法

public static  ServiceLoader load(Class service,
                                        ClassLoader loader)

返回connection对象

再次定位到这行代码

Connection connection= DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","123456");

查看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);
    }

    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; //获取当前线程的类加载器
    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;

    for(DriverInfo aDriver : registeredDrivers) {  //遍历注册信息
        // If the caller does not have permission to load the driver then
        // skip it.
        if(isDriverAllowed(aDriver.driver, callerCL)) {  //判断该驱动是否是callerCL加载器加载的
            try {
                println("    trying " + aDriver.driver.getClass().getName());
                Connection con = aDriver.driver.connect(url, info);  //通过驱动器返回connection对象
                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");
}

以上通过备注我们知道,这个是一个获取connection对象的过程,先是遍历registerDrivers集合获取每个驱动器,然后进行验证,成功后返回该驱动器。

到这里jdbc的源码解析就结束了,因为sql包中很多都是接口需要子类进行实现,所以接下来要说的都是mysql继承接口中的实现,我也不细说大概点一下。

3.获取PreparedStatement对象

PreparedStatement prepareStatement=connection.prepareStatement("select * from student");

这里我们通过connection得到了preparedstatement,preparedstatement继承自statement,里面保存了sql语句对象,并提供了查询sql的方法。

4.获取ResultSet对象

ResultSet resultSet=prepareStatement.executeQuery();

在这里statement对象调用了executeQuery方法,里面将会执行发送sql以及获取数据的操作,在mysql中是通过Socket对象进行操作的。方法返回ResultSet,存储了查询的结果。

获取resultSet对象后通过next方法移动游标定位信息

while(resultSet.next()){
   System.out.println(resultSet.getString("id")+":"+resultSet.getString("studname"));
}

以上就是jdbc源码的讲解

你可能感兴趣的:(java jdk8 源码解析之sql包:JDBC源码解析)