JDBC相关
jdk8 源码解析之 sql 包:JDBC 源码解析
参考连接:https://blog.csdn.net/mxy88888/article/details/94315198
在开发项目时我们经常会需要与数据库进行交互,为了统一标准,在 java jdk 中提供了一组与数据库交互的 api(java.sql.*),每个厂商通过继承实现 sql 包下的接口和类完成与数据库交互的工作(例如 mysql-connector-java)。以 mysql 为例:
1 | public static void main(String[] arg) throws Exception { |
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 对象
下面我们来一步步解析查询数据库的过程:
驱动类加载
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
68static {
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 内执行,是为了跳过
虚拟机权限验证
。返回 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 继承接口中的实现,我也不细说大概点一下。
获取 PreparedStatement 对象
1
PreparedStatement prepareStatement=connection.prepareStatement("select * from student");
这里我们通过 connection 得到了 preparedstatement,preparedstatement 继承自 statement,里面保存了 sql 语句对象,并提供了查询 sql 的方法。
获取 ResultSet 对象
ResultSet resultSet=prepareStatement.executeQuery();
在这里 statement 对象调用了 executeQuery 方法,里面将会执行发送 sql 以及获取数据的操作,在 mysql 中是通过 Socket 对象进行操作的。方法返回 ResultSet,存储了查询的结果。获取 resultSet 对象后通过 next 方法移动游标定位信息。
1
2
3
4while(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:为了指示一个警告而抛出