问题抛出
jdbc只是接口,数据库驱动是其实现,是如何找到驱动并进行加载使用?类路径有多个数据库驱动实现的时候,该选择哪个?
jdbc模板示例
下面是jdbc接入的范例,首先将数据库驱动添加到工程中,其中加载驱动程序的代码是可选的Class.forName("com.mysql.jdbc.Driver");
,可以不写。那么问题来了,程序是如何找到驱动程序并完成加载呢?
public class DbUtil {
public static final String URL = "jdbc:mysql://localhost:3306/imooc";
public static final String USER = "liulx";
public static final String PASSWORD = "123456";
public static void main(String[] args) throws Exception {
//1.加载驱动程序
Class.forName("com.mysql.jdbc.Driver");
//2. 获得数据库连接
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
//3.操作数据库,实现增删改查
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT user_name, age FROM imooc_goddess");
//4. 如果有数据,rs.next()返回true
while(rs.next()){
System.out.println(rs.getString("user_name")+" 年龄:"+rs.getInt("age"));
}
}
}
驱动类加载的时机
首先,通过DriverManager
找到数据库驱动,并进行加载。
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
DriverManager
用来管理类路径中的数据库驱动。如何找到数据库驱动类,关键点在于,ServiceLoader
SPI,加载Driver.class
。这个loadInitialDrivers是DriverManager加载时触发的。
具体代码如下
public class DriverManager{
private DriverManager(){}
static {
loadInitialDrivers();//加载驱动
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类
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
}
ServiceLoader
一般使用接口的实现类都是静态new一个实现类赋值给接口引用,如下:
HelloService service = new HelloImpl();
如果需要动态的获取一个接口的实现类呢?全局扫描全部的Class,然后判断是否实现了某个接口?代价太大,一般不会这么做。一种合适的方式就是使用配置文件,把实现类名配置在某个地方,然后读取这个配置文件,获取实现类名。JDK提供的ServiceLoader就是这种方式。
具体用法,就是在实现类的jar包的META-INF下新建一个文件夹services,并在services下新建一个文件,以接口的全限定名为文件名,内容为实现类的全限定名。
怎么找到驱动类?
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
DriverManager加载的时候,会load Driver.class,Driver.class的全限定名是java.sql.Driver
。
也就是通过ServiceLoader机制,完成了驱动类的加载
如何选择驱动类?
当类路径有多个驱动类时,通过配置来匹配驱动,完成驱动的初始化。
public static final String URL = "jdbc:mysql://localhost:3306/imooc";
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
url指定了采用的协议是jdbc:mysql
,