目录
环境
- 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()
方法显式注册驱动,则会把驱动类放到列表的第一个,优先使用它来连接数据库。