JDBC 基本流程源码分析

本文详细介绍了JDBC的基本流程,包括加载数据库驱动、建立连接、执行SQL语句和获取响应的过程。通过SPI加载MySQL驱动,建立Socket连接,使用Statement和PreparedStatement执行查询与更新操作。此外,还深入解析了MySQL驱动协议,如Failover、Load Balancing和Replication策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近为了阅读 MyBatis 源码,大概看了下 JDBC 的代码,了解在不使用 ORM 框架的情况下 Java 实现 SQL 查询的原理。

JDBC 定义如下:

JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行 SQL 语句的 Java API,可以为多种关系数据库提供统一访问,它由一组用 Java 语言编写的类和接口组成。JDBC 提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。

也就是说 JDBC 是 SUN 公司提出来的一系列规范,但它只定义了接口规范,具体的实现则交给各个数据库的厂商去做。

1. 使用流程

JDBC 流程:

  1. 通过 SPI 加载 Driver 驱动类。
  2. 建立数据库连接,获取 Connection 连接对象。
  3. 通过连接创建 Statement 对象,执行 SQL 语句,获取返回结果。
  4. 释放资源。

在 Maven 中添加对 mysql 驱动的依赖:

<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<version>8.0.23</version>
</dependency>

数据库表:
学生表

1.1 普通查询

@Test
public void query() throws Exception {
   
	// 打印至控制台
	DriverManager.setLogWriter(new PrintWriter(System.out));
	// 建立连接
	Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testdb", "test", "test");
	// 执行 SQL 查询,获取返回结果
	Statement statement = conn.createStatement();
	statement.setQueryTimeout(60);
	ResultSet resultSet = statement.executeQuery("select * from t_student");
	while (resultSet.next()) {
   
		System.out.println("id:" + resultSet.getInt(1) + " address:" + resultSet.getString(2) + " name:" + resultSet.getString(4));
	}
}

执行结果:

DriverManager.getConnection("jdbc:mysql://localhost:3306/testdb")
    trying com.mysql.cj.jdbc.Driver
getConnection returning com.mysql.cj.jdbc.Driver
id:1 address:hunan name:zhangsan
id:2 address:guangzhou name:lisi
id:3 address:四川 name:大大

1.2 参数查询

@Test
public void queryByParam() throws SQLException {
   
	DriverManager.setLogWriter(new PrintWriter(System.out));
	// 建立连接
	Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testdb", "test", "test");
	// 执行 SQL 查询,获取返回结果
	PreparedStatement preparedStatement = conn.prepareStatement("select * from t_student where id = ? and age = ?");
	preparedStatement.setString(1, "1");
	preparedStatement.setInt(2, 11);
	ResultSet resultSet = preparedStatement.executeQuery();
	while (resultSet.next()) {
   
		System.out.println("id:" + resultSet.getInt(1) + " address:" + resultSet.getString(2) + " name:" + resultSet.getString(4));
	}
}

1.3 批量更新

@Test
public void updateBatch() throws SQLException {
   
	DriverManager.setLogWriter(new PrintWriter(System.out));
	Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testdb", "test", "test");
	PreparedStatement preparedStatement = conn.prepareStatement("update t_student set age = ? where id = ?");

	preparedStatement.setInt(1, 10);
	preparedStatement.setString(2, "1");
	preparedStatement.addBatch();

	preparedStatement.setInt(1, 10);
	preparedStatement.setString(2, "2");
	preparedStatement.addBatch();

	int[] result = preparedStatement.executeBatch();
	System.out.println("result = " + result.length);
}

2. 源码解析

2.1 加载驱动

主要流程:

  1. 通过 SPI 加载 MySQL 驱动包中的 Driver 类。
  2. 将 Driver 类注册到 JDBC 的 DriverManager 之中。

2.1.1 SPI 加载驱动类

DriverManager 中定义了静态代码块,会通过 SPI 来加载数据库驱动类。

java.sql.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");
}

java.sql.DriverManager#loadInitialDrivers

private static void loadInitialDrivers() {
   
	// ...
	AccessController.doPrivileged(new PrivilegedAction<Void>() {
   
		public Void run() {
   

			// 采用 SPI 机制加载数据库驱动
			ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); 
			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;
		}
	});
	// ...
}

通过 SPI,加载配置文件 META-INF/services/java.sql.Driver,由于项目中具有 MySql 驱动的 jar 包,这里是读取到配置文件:
mysql-connector-java-8.0.23.jar!\META-INF\services\java.sql.Driver
文件中的内容为 com.mysql.cj.jdbc.Driver,反射获取得到该类,并进行实例化。

2.1.2 注册驱动类

实例化 MySQL 驱动的时候,会通过 DriverManager#registerDriver 方法来注册驱动。

com.mysql.cj.jdbc.Driver

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
   
    //
    // Register ourselves with the DriverManager
    //
    static {
   
        try {
   
            java.sql.DriverManager.registerDriver(new Driver()); // 注册驱动
        } catch (SQLException E) {
   
            throw new RuntimeException("Can't register driver!");
        }
    }

    /**
     * Construct a new driver and register it with DriverManager
     * 
     * @throws SQLException
     *             if a database error occurs.
     */
    public Driver() throws SQLException {
   
        // Required for Class.forName().newInstance()
    }
}

其底层实现是,将 MySQL 驱动 Driver 对象包装为 DriverInfo,存储在 DriverManager#registeredDrivers 集合中。

java.sql.DriverManager#registerDriver

// List of registered JDBC drivers
private final 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值