JDBC相关

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

参考连接:https://blog.csdn.net/mxy88888/article/details/94315198

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

1
2
3
4
5
6
7
8
9
10
11
12
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();
}

JDBC (Java Database Connectivity) API,即 Java 数据库编程接口,是一组标准的 Java 语言中的接口和类,使用这些接口和类,Java 客户端程序可以访问各种不同类型的数据库。比如建立数据库连接、执行 SQL 语句进行数据的存取操作。

JDBC 规范采用接口和实现分离的思想设计了 Java 数据库编程的框架。接口包含在 java.sql 及 javax.sql 包中,其中 java.sql 属于 JavaSE,javax.sql 属于 JavaEE。这些接口的实现类叫做数据库驱动程序,由数据库的厂商或其它的厂商或个人提供。

为了使客户端程序独立于特定的数据库驱动程序,JDBC 规范建议开发者使用基于接口的编程方式,即尽量使应用仅依赖 java.sql 及 javax.sql 中的接口和类。

JDBC 驱动程序是各个数据库厂家根据 JDBC 的规范制作的 JDBC 实现类.

主要涉及到的类有:

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

    Class.forName("com.mysql.jdbc.Driver")
    当我们看到这行代码时我们可能会有些疑惑:为什么开始要加载初始化这个驱动?那我们先看看它里面有什么。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    //Driver 类
    static {
    try {
    java.sql.DriverManager.registerDriver(new Driver()); //注册驱动
    } catch (SQLException E) {
    throw new RuntimeException("Can't register driver!");
    }
    }
    //DriverManager 类
    private final static CopyOnWriteArrayList<DriverInfo> 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 内源码,其中有一段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    static {
    loadInitialDrivers(); /加载初始化其他驱动类
    println("JDBC DriverManager initialized");
    }

    private static void loadInitialDrivers() {
    String drivers;
    try {
    drivers = AccessController.doPrivileged(new PrivilegedAction<String>() { //跳过权限验证从系统变量中获取驱动
    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<Void>() {
    public Void run() {

    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);//通过ServiceLoader动态加载驱动类
    Iterator<Driver> 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(参考 Java 的 SPI)动态获取驱动类对象完成注册,两种方式都在 AccessController.doPrivileged 内执行,是为了跳过虚拟机权限验证

  2. 返回 connection 对象
    再次定位到这行代码

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

    查看 getConnection 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    @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 对象

    1
    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 方法移动游标定位信息。

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

    以上就是 jdbc 源码的讲解。

java.sql 里面有什么?

java.sql 包中包含用于以下方面的 API:

  • 通过 DriverManager 实用程序建立与数据库的连接 bai
  • DriverManager 类:建立与驱动程序的连接
  • SQLPermission 类:代码在 Security Manager(比如 applet)中运行时提供权限,试图通过 DriverManager 设置一个记录流
  • Driver 接口:提供用来注册和连接基于 JDBC 技术(“JDBC 驱动程序”)的驱动程序的 API,通常仅由 DriverManager 类使用
  • DriverPropertyInfo 类:提供 JDBC 驱动程序的属性,不是供一般用户使用的向数据库发送 SQL 语句
  • Statement:用于发送基本 SQL 语句
  • PreparedStatement:用于发送准备好的语句或基本 SQL 语句(派生自 Statement)
  • CallableStatement:用于调用数据库存储过程(派生自 PreparedStatement)
  • Connection 接口:提供创建语句以及管理连接及其属性的方法
  • Savepoint:在事务中提供保存点
  • 获取和更新查询的结果
  • ResultSet 接口
  • SQL 类型到 Java 编程语言中的类和接口的标准映射关系
    • Array 接口:SQL ARRAY 的映射关系
    • Blob 接口:SQL BLOB 的映射关系
    • Clob 接口:SQL CLOB 的映射关系
    • Date 类:SQL DATE 的映射关系
    • …..
  • 元数据
    • DatabaseMetaData 接口:提供有关数据库的信息
    • ResultSetMetaData 接口:提供有关 ResultSet 对象的列的信息
    • ParameterMetaData 接口:提供有关 PreparedStatement 命令的参数的信息
  • 异常
    • SQLException:由大多数方法在访问数据出问题时抛出,以及因为其他原因由其他一些方法抛出
    • SQLWarning:为了指示一个警告而抛出