本篇文章主要介绍了JDBC驱动注册的几种常用方式和Java是如何动态的加载并注册多个JDBC数据库驱动的。


JDBC驱动注册

JDBC提供了独立于数据库的统一APIMySQLOracle等数据库公司都可以基于这个标准接口来进行开发。包括java .sql包下的DriverConnectionStatementResultSetJDBC提供的接口。而DriverManager是用于管理JDBC驱动的服务类,主要用于获取Connection对象(此类中全是静态方法)。

当我们查看API,在Driver接口中,明确要求:Driver接口是每个驱动程序类必须实现的接口。Java SQL框架允许多个数据库驱动程序。每个驱动程序都应该提供一个实现Driver接口的类。并且明确:在加载某一Driver类时,它应该创建自己的实例并向 DriverManager注册该实例。因此,我们有了以下三种常用的使用方法:

1.反射

1
Class.forName("com.mysql.jdbc.Driver");

这种方式是不是很眼熟,相信这种也是大家最常见和最常用的方式了,而且我一般也推荐使用这种方式,不会对具体的驱动类产生依赖(就是不用import package了)。

2.DriverManager

1
2
Driver driver = new Driver(); //com.mysql.jdbc.Driver  
DriverManager.registerDriver(driver);

1
DriverManager.registerDriver(new com.mysql.jdbc.Driver());

这种方式会造成DriverManager中产生两个一样的驱动(其实就是实例化了两次),并会对具体的驱动类产生依赖。正常情况下我是不推荐使用的,但是在特定需求下还是很有作用的,具体的我会在后面详叙。

3.系统属性

1
System.setProperty("jdbc.drivers","com.mysql.jdbc.Driver");

这种方式是通过系统的属性设置注册驱动,如果要注册多个驱动,则System.setProperty("jdbc.drivers", "com.mysql.jdbc.Driver:com.oracle.jdbc.Driver");,虽然不会对具体的驱动类产生依赖;但注册不太方便,所以很少使用。

4.综合分析

上面我们说了三种常用的JDBC驱动的注册方式,其中因为第三种很少使用,所以我们就不过多讨论了,这里我们主要来对比一下第一种和第二种注册方式的异同。这两种注册方式有什么不同呢?第一种方式是利用反射机制来完成的,直接看的话,我们会想Class.forName (driverClass)只能帮助我们得到DriverClass对象啊,为什么会帮我们完成注册了呢。从上边对Driver()API的查阅,API
要求:在加载某一Driver类时,它应该创建自己的实例并向DriverManager注册该实例。所以我们猜想是在类加载时,就自动完成了注册。而第二种方式,相对比较好理解,就是先创建数据库驱动,然后调用registerDriver()方法完成注册。下边去具体看一下源码来分析一下:

第一种方式,这种方式是怎么通过只要获得DriverClass对象就可以完成注册呢,下边看一下其com.mysql.jdbc.Driver 的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
// ~ Static fields/initializers
// ---------------------------------------------
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
}
catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
}

从上边可以看到,它是用静态代码块实现的。根据类加载机制,当执行Class.forName(driverClass)获取其Class对象时,com.mysql.jdbc.Driver就会被JVM加载连接,并进行初始化,初始化就会执行静态代码块,也就会执行下边这句代码:

1
java.sql.DriverManager.registerDriver(new Driver());

大家有没有发现这已经和第二种方式一样了,所以有没有想到点什么( ̄︶ ̄)

第二种方法,我们看下JDK1.7下的DriverMangerregisterDriver()方法:

1
2
3
4
5
6
7
8
9
10
11
public static synchronized void registerDriver(java.sql.Driver driver) 
throws SQLException {
/* Register the driver if it has not already been added to our list */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver:" + driver);
}

从其源码,可以看到DriverManger将要注册的驱动程序信息封装到了DriverInfo中,然后放进了一个List中。在后边获得连接时会再用到。

1
2
// List of registered JDBC drivers  
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();

Ok,分析完两种方式后,相信聪明的童鞋已经知道了结果。这里我们在总结一下,对于上边的两种驱动注册方式,我们一般采用第一种方式,因为第二种方式有以下两个问题:

(1)由于实例化了com.mysql.jdbc.Driver.class,导致必须导入该类(就是要把这个类import进去),从而具体驱动产生了依赖,不方便扩展代码。

(2)DriverManager.registerDriver(new com.mysql.jdbc.Driver());,实例化的时候注册了一次,加载的时候其在内部也执行静态代码块,这相当于实例化了两个Driver对象。


动态加载JDBC驱动

1.问题背景

在同一套系统中,要支持连接访问各种流行的数据库,以及同一数据库的不同版本,例如:Oracle 9iOracle 10gOracle 11gOracle 12cSQL Server 2000SQL Server 2005SQL Server 2008SQL Server 2012等,其中就会碰到一些问题,就是不同的数据库,数据库驱动肯定不同,对于这个问题到好解决,只需要将相应的驱动加入即可。然而对于同种数据库,不同版本时,而且不同版本的数据库驱动不仅不兼容,同时存在还会出现冲突,例如,能满足SQL Server 2000的驱动,就不能满足SQL Server 2012,而能满足SQL Server 2012的驱动,就不能满足SQL Server 2000。对于这种问题,面前能想到的解决方案就是动态加载数据库驱动,当用到某种数据库时,就加载其对应的数据库驱动,使用完以后就卸载驱动。

2.代码实现

在此只列出核心代码,就是动态加载数据库驱动的类,只是此处暂时没有考虑到数据库连接池的问题,当选择动态加载数据库驱动时,数据库连接池需要自己实现。这里只是提供了一个思路,并不一定是最优实现。

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
76
package com.mygu.dynamic.service.impl;
import com.mygu.dynamic.model.DataSourceInfo;
import com.mygu.dynamic.service.IDynamicLoaderService;
import org.springframework.stereotype.Service;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.annotation.PostConstruct;
/**
* 动态加载JDBC数据库驱动服务类
*/
@Service
public class DynamicLoaderService implements IDynamicLoaderService {
// 保存数据库连接信息
private Map<String, DataSourceInfo> dataSourceInfos = null;
// 保存数据库相应驱动JAR的路径
private Map<String, String> paths = null;
// 缓存对应的Driver对象
private Map<String, Driver> drivers = null;
private URLClassLoader classLoader;
/**
* 初始化方法
*
* @throws Exception
*/
@PostConstruct
public void init() throws Exception {
dataSourceInfos = new HashMap<String, DataSourceInfo>();
// 此处只已Oracle 9i和Oracle 10g为例,其它数据库信息同理的方式增加即可
DataSourceInfo oracle9Info = new DataSourceInfo();
oracle9Info.setDriver("oracle.jdbc.driver.OracleDriver");
oracle9Info.setUrl("jdbc:oracle:thin:@192.168.0.101:1521:ORACLE");
oracle9Info.setUsername("system");
oracle9Info.setPassword("system");
dataSourceInfos.put("oracle9", oracle9Info);
DataSourceInfo oracle10Info = new DataSourceInfo();
oracle10Info.setDriver("oracle.jdbc.driver.OracleDriver");
oracle10Info.setUrl("jdbc:oracle:thin:@192.168.0.102:1521:orcl");
oracle10Info.setUsername("system");
oracle10Info.setPassword("system");
dataSourceInfos.put("oracle10", oracle10Info);
// 将数据库对应驱动jar放置容器中
paths = new HashMap<String, String>();
paths.put("oracle9", "/driver/ojdbc6.jar");
paths.put("oracle10", "/driver/ojdbc14.jar");
// 初始化drivers
drivers = new HashMap<String, Driver>();
}
@Override
public Connection getConnection(String dbType) throws Exception {
Connection conn = null;
Driver driver = drivers.get(dbType);
DataSourceInfo dataSourceInfo = dataSourceInfos.get(dbType);
if (driver == null) {
URL jarUrl = this.getClass().getResource(paths.get(dbType));
classLoader = new URLClassLoader(new URL[] { jarUrl });
Class<?> driverClass = classLoader.loadClass(dataSourceInfo.getDriver());
driver = (Driver) driverClass.newInstance();
drivers.put(dbType, driver);
}
// 注册驱动
DriverManager.registerDriver(driver);
Properties properties = new Properties();
properties.put("user", dataSourceInfo.getUsername());
properties.put("password", dataSourceInfo.getPassword());
conn = DriverManager.getConnection(dataSourceInfo.getUrl(), properties);
// 卸载驱动
DriverManager.deregisterDriver(driver);
return conn;
}
}

3.其他方案

上面提供了一个自己如何通过动态注册和卸载JDBC驱动的方式来实现的思路,并且没有实现连接池。其实在现在很多开源的数据库连接池的框架中,已经自带实现了动态数据源的注册和卸载,并且还包含SQL的使用工具、性能监测等很多功能。如果想详细了解的话,这里我推荐一个阿里开源的Druid连接池框架,这是一个支持多线程和异步的连接池框架,比之其他开源的主流连接池,例如C3P0DBCP等单线程框架性能和可扩展性都要好很多,有兴趣的可以深入了解一下。