本篇文章主要介绍了JDBC
驱动注册的几种常用方式和Java
是如何动态的加载并注册多个JDBC
数据库驱动的。
JDBC驱动注册
JDBC
提供了独立于数据库的统一API
,MySQL
、Oracle
等数据库公司都可以基于这个标准接口来进行开发。包括java .sql
包下的Driver
、Connection
、Statement
、ResultSet
是JDBC
提供的接口。而DriverManager
是用于管理JDBC
驱动的服务类,主要用于获取Connection
对象(此类中全是静态方法)。
当我们查看
API
,在Driver
接口中,明确要求:Driver
接口是每个驱动程序类必须实现的接口。Java SQL
框架允许多个数据库驱动程序。每个驱动程序都应该提供一个实现Driver
接口的类。并且明确:在加载某一Driver
类时,它应该创建自己的实例并向DriverManager
注册该实例。因此,我们有了以下三种常用的使用方法:
1.反射
1 | Class.forName("com.mysql.jdbc.Driver"); |
这种方式是不是很眼熟,相信这种也是大家最常见和最常用的方式了,而且我一般也推荐使用这种方式,不会对具体的驱动类产生依赖(就是不用import package了)。
2.DriverManager
1 | Driver driver = new Driver(); //com.mysql.jdbc.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)
只能帮助我们得到Driver
的Class
对象啊,为什么会帮我们完成注册了呢。从上边对Driver()
的API
的查阅,API
要求:在加载某一Driver
类时,它应该创建自己的实例并向DriverManager
注册该实例。所以我们猜想是在类加载时,就自动完成了注册。而第二种方式,相对比较好理解,就是先创建数据库驱动,然后调用registerDriver()
方法完成注册。下边去具体看一下源码来分析一下:
第一种方式,这种方式是怎么通过只要获得
Driver
的Class
对象就可以完成注册呢,下边看一下其com.mysql.jdbc.Driver
的源码:
1 | public class Driver extends NonRegisteringDriver implements java.sql.Driver { |
从上边可以看到,它是用静态代码块实现的。根据类加载机制,当执行
Class.forName(driverClass)
获取其Class
对象时,com.mysql.jdbc.Driver
就会被JVM
加载连接,并进行初始化,初始化就会执行静态代码块,也就会执行下边这句代码:
1 | java.sql.DriverManager.registerDriver(new Driver()); |
大家有没有发现这已经和第二种方式一样了,所以有没有想到点什么( ̄︶ ̄)
第二种方法,我们看下
JDK1.7
下的DriverManger
的registerDriver()
方法:
1 | public static synchronized void registerDriver(java.sql.Driver driver) |
从其源码,可以看到
DriverManger
将要注册的驱动程序信息封装到了DriverInfo
中,然后放进了一个List
中。在后边获得连接时会再用到。
1 | // List of registered JDBC drivers |
Ok,分析完两种方式后,相信聪明的童鞋已经知道了结果。这里我们在总结一下,对于上边的两种驱动注册方式,我们一般采用第一种方式,因为第二种方式有以下两个问题:
(1)由于实例化了
com.mysql.jdbc.Driver.class
,导致必须导入该类(就是要把这个类import进去),从而具体驱动产生了依赖,不方便扩展代码。
(2)
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
,实例化的时候注册了一次,加载的时候其在内部也执行静态代码块,这相当于实例化了两个Driver
对象。
动态加载JDBC驱动
1.问题背景
在同一套系统中,要支持连接访问各种流行的数据库,以及同一数据库的不同版本,例如:
Oracle 9i
、Oracle 10g
、Oracle 11g
、Oracle 12c
、SQL Server 2000
、SQL Server 2005
、SQL Server 2008
、SQL Server 2012
等,其中就会碰到一些问题,就是不同的数据库,数据库驱动肯定不同,对于这个问题到好解决,只需要将相应的驱动加入即可。然而对于同种数据库,不同版本时,而且不同版本的数据库驱动不仅不兼容,同时存在还会出现冲突,例如,能满足SQL Server 2000
的驱动,就不能满足SQL Server 2012
,而能满足SQL Server 2012
的驱动,就不能满足SQL Server 2000
。对于这种问题,面前能想到的解决方案就是动态加载数据库驱动,当用到某种数据库时,就加载其对应的数据库驱动,使用完以后就卸载驱动。
2.代码实现
在此只列出核心代码,就是动态加载数据库驱动的类,只是此处暂时没有考虑到数据库连接池的问题,当选择动态加载数据库驱动时,数据库连接池需要自己实现。这里只是提供了一个思路,并不一定是最优实现。
1 | package com.mygu.dynamic.service.impl; |
3.其他方案
上面提供了一个自己如何通过动态注册和卸载
JDBC
驱动的方式来实现的思路,并且没有实现连接池。其实在现在很多开源的数据库连接池的框架中,已经自带实现了动态数据源的注册和卸载,并且还包含SQL
的使用工具、性能监测等很多功能。如果想详细了解的话,这里我推荐一个阿里开源的Druid
连接池框架,这是一个支持多线程和异步的连接池框架,比之其他开源的主流连接池,例如C3P0
、DBCP
等单线程框架性能和可扩展性都要好很多,有兴趣的可以深入了解一下。