解决JDBC的class.forName()问题
作者:蓝黑2020
环境
- Ubuntu 22.04
- IntelliJ IDEA 2022.1.3
- JDK 17.0.3
- Db2 v11.5.0.0
- MySQL Ver 8.0.30
准备
Db2
在Db2的 sample
数据库中,创建表 t1
,并插入一些数据。如下:
➜ ~ db2 "select * from t1" C1 C2 C3 ----------- ----------- ----------- 1 444 - 2 222 - 3 333 - 3 record(s) selected.
MySQL
在MySQL的 repo
数据库中,创建表 t1
,并插入一些数据。如下:
mysql> select * from t1; +------+-------+ | c1 | c2 | +------+-------+ | 1 | 9800 | | 2 | 10200 | +------+-------+ 2 rows in set (0.00 sec)
代码
创建Maven项目 test0924
。
修改 pom.xml
文件,添加依赖:
...... <!-- https://mvnrepository.com/artifact/com.ibm.db2/jcc --> <dependency> <groupId>com.ibm.db2</groupId> <artifactId>jcc</artifactId> <version>11.5.7.0</version> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> ......
如上,在项目添加了Db2和MySQL的JDBC驱动。
Db2
创建类 Test0924_Db2
:
package pkg1; import java.sql.*; public class Test0924_Db2 { public static void main(String[] args) throws ClassNotFoundException { // Class.forName("com.ibm.db2.jcc.DB2Driver"); try ( Connection connection = DriverManager.getConnection("jdbc:db2://localhost:50000/sample", "db2inst1", "passw0rd"); Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("select * from t1"); ) { // System.out.println(connection.getClass().getName()); while (rs.next()) { System.out.println(rs.getInt(1)); } } catch (SQLException e) { throw new RuntimeException(e); } } }
运行程序,结果如下:
1
2
3
MySQL
创建类 Test0924_Mysql
:
package pkg1; import java.sql.*; public class Test0924_Mysql { public static void main(String[] args) throws ClassNotFoundException { // Class.forName("com.mysql.jdbc.Driver"); try ( Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/repo", "root", "123456"); Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("select * from t1"); ) { // System.out.println(connection.getClass().getName()); while (rs.next()) { System.out.println(rs.getInt(1)); } } catch (SQLException e) { throw new RuntimeException(e); } } }
运行程序,结果如下:
1
2
分析
JDBC
比较两个Java文件可见,连接Db2和连接MySQL的方式非常类似,唯一的区别在于,调用 DriverManager.getConnection()
方法时,传入的URL不同:
- Db2:
jdbc:db2://localhost:50000/sample
- MySQL:
jdbc:mysql://localhost:3306/repo
更确切的说,只是协议的不同: db2
VS. mysql
。
我们知道,JDBC是一套标准,各个厂商分别有着自己的实现,也就是各自的JDBC驱动。这也就是为什么一开始,我们就先引入Db2和MySQL的JDBC驱动。
JDBC中几个重要的类:
java.sql.DriverManager
java.sql.Connection
java.sql.Statement
java.sql.ResultSet
注意: Connection
、 Statement
、 ResultSet
都是需要关闭的,一种方法是在 finally
块里显式调用其 close()
方法。本例中,使用了Java 7引入的 try()
块来自动释放资源(它们都实现了 AutoCloseable
接口)。
class.forName()
以前我们学习JDBC的时候,被告知第一步要先使用 Class.forName()
方法,导入特定的JDBC驱动。
但是通过本文的两个例子,我们看到,即使省略这一步,也没有问题,DriverManager能够自动找到合适的驱动。
那么问题来了:
- 调用
Class.forName()
方法,到底干了什么? - 为什么本文中不调用该方法也没问题?
我们知道,如果某个类之前没有被使用过,则调用 Class.forName()
方法,会做几件事情,包括实例化该类的Class对象,并且执行其static块,等等。
对于JDBC驱动,以Db2驱动为例,查看 com.ibm.db2.jcc.DB2Driver
类,可以找到如下代码:
static { DB2BaseDataSource.class.getClass(); try { registeredDriver__ = new DB2Driver(); DriverManager.registerDriver(registeredDriver__); } catch (SQLException var1) { ap.f = lr.a(b7.a(DB2Driver.class, (ds)null, ErrorKey.ERROR_REGISTER_WITH_DRIVER_MGR, "10032"), ap.f); } }
可见,调用了 DriverManager.registerDriver()
方法注册了Db2的驱动。
同理,对于MySQL,它的驱动类 com.mysql.cj.jdbc.Driver
(是 com.mysql.jdbc.Driver
类的父类)里有如下代码:
static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } }
可见,类似的,也是调用了 DriverManager.registerDriver()
方法注册了MySQL的驱动。
由此,我们知道,调用 class.forName()
方法来装载驱动,其作用是注册了该驱动。
那么为什么本文中不调用方法也没问题呢?
java.sql.Connection
是一个接口,我们通过打印 connection.getClass().getName()
来看看具体的类名(参见代码中的注释部分)。
- Db2:
com.ibm.db2.jcc.t4.b
- MySQL:
com.mysql.cj.jdbc.ConnectionImpl
可见,即使不通过 class.forName()
方法来显式注册指定的驱动,而直接调用 DriverManager.getConnection()
方法,则根据传入的URL不同,也能获取正确的数据库连接。
可以去查看DriverManager的源码,大致如下:
...... for (DriverInfo aDriver : registeredDrivers) { // If the caller does not have permission to load the driver then // skip it. 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; } } ......
也就是说,它会先生成驱动的列表,然后遍历列表,根据传入的URL,尝试使用当前驱动来连接数据库,如果能连上,就OK,否则就尝试下一个驱动。
当然,如果调用 Class.forName()
方法显式注册驱动,则会把驱动类放到列表的第一个,优先使用它来连接数据库。
到此这篇关于关于JDBC的class.forName()问题的文章就介绍到这了,更多相关JDBC的class.forName()内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!