这里主要是因为最近使用jdbc连接数据库时,发现相比之前一般的连接过程,现在竟然不用加载驱动也可以了。这里研究记录下。
JDBC
JDBC是一个连接数据库的Java API,包含了相关的接口和类。
但是,他不提供针对具体数据库(MySQL、MS、Oracle)的实际操作,而只是提供了接口,以及调用框架。
和具体数据库的直接交互由对应的驱动程序完成,比如mysql的mysql-connector、oracle的ojdbc、MS的sqljdbc等。
也就是说它实际上是一种规范。目的是为了让各个数据库开发商为Java程序员提供标准的数据访问类和接口,使得独立于DBMS的Java应用程序的开发成为可能(
JDBC的组成如下:
JDBC API (统一的应用接口)
JDBC Driver Manager(驱动程序管理器)
JDBC 数据库驱动程序 驱动本质就是一个Java类,这个类实现了JavaAPI定义的接口
1、加载JDBC驱动程序:
Class.forName("com.mysql.jdbc.Driver") ;
2、提供JDBC连接的URL
String url = jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8
3、创建数据库的连接
Connection con = DriverManager.getConnection(url , username , password ) ;
4、创建一个Statement
PreparedStatement pstmt = con.prepareStatement(sql) ;
5、执行SQL语句
ResultSet rs = stmt.executeQuery("SELECT * FROM ...") ;
6、处理结果
while(rs.next()){ //do something }
7、关闭JDBC对象
通过上面一般的连接步骤,我们知道,驱动的加载是由Class.forName 方法完成的。
那么Class.forName是具体怎样加载的呢?
实际上完成驱动的加载实际上是由具体的数据库驱动类的静态初始化块完成的。
这里看一下mysql的驱动类的代码:
public class Driver extends NonRegisteringDriver implements java.sql.Driver { // // Register ourselves with the DriverManager // static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } }
由于JVM对类的加载有一个逻辑是:在类被需要的时候,或者首次调用的时候就会把类加载到JVM。反过来也就是:如果类没有被需要的时候,一般是不会被加载到JVM的。
当连接数据库的时候我们调用了Class.forName语句之后,数据库驱动类被加载到JVM,那么静态初始化块就会被执行,从而完成驱动的注册工作,也就是注册到了JDBC的DriverManager类中。
由于是静态初始化块中完成的加载,所以也就不必担心驱动被加载多次,原因可以参考单例模式相关的知识。
抛弃Class.forName
在JDBC 4.0之后实际上我们不需要再调用Class.forName来加载驱动程序了,我们只需要把驱动的jar包放到工程的类加载路径里,那么驱动就会被自动加载。
这个自动加载采用的技术叫做SPI,数据库驱动厂商也都做了更新。
可以看一下jar包里面的META-INF/services目录,里面有一个java.sql.Driver的文件,文件里面包含了驱动的全路径名。
比如mysql-connector里面的内容:
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
那么SPI技术又是在什么阶段加载的数据库驱动呢?
看一下JDBC的DriverManager类就知道了。
public class DriverManager { static { loadInitialDrivers();//......1 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"); } }); } catch (Exception ex) { drivers = null; } AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);//.....2 Iterator 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;
}
上述代码片段标记…1的位置是在DriverManager类加载是执行的静态初始化块,这里会调用loadInitialDrivers方法。
再看loadInitialDrivers方法里面标记…2的位置,这里调用的 ServiceLoader.load(Driver.class); 就会加载所有在META-INF/services/java.sql.Driver文件里边的类到JVM内存,完成驱动的自动加载。
这就是SPI的优势所在,能够自动的加载类到JVM内存。这个技术在阿里的dubbo框架里面也占到了很大的分量,有兴趣的朋友可以看一下dubbo的代码,或者百度一下dubbo的扩展机制。
而且这里通过迭代器遍历了一遍就实现加载了。
跟了代码发现ServiceLoader在Iterable的实现中进行了初始化,代码可以参考ServiceLoader类的nextService方法
private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<?> c = null; try { c = Class.forName(cn, false, loader); } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); } if (!service.isAssignableFrom(c)) { fail(service, "Provider " + cn + " not a subtype"); } try { S p = service.cast(c.newInstance()); providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // This cannot happen }