通过系统外置数据库驱动包创建连接

背景

类加载机制
最近在做一个新项目,项目中需要实现多种数据源执行SQL语句。由于数据源的来源多样,并且同一种数据源存在驱动包版本不一样的情况,所以打算将驱动包外置。即,将不同类型数据源的驱动包放在系统某一个路径下,在程序中根据用户配置的驱动包的路径、驱动包的驱动类名来获取一个数据库连接。

功能实现

1.先来看下最常用的代码实现:

    /** step1: 首先加载oracle的驱动类
    	step2: 初始化的驱动类,在静态代码块中,将驱动注册进DriverManager里
    **/
    Class.forName("oracle.jdbc.OracleDriver");
    // 获取数据库连接
    Connection conn = DriverManager.getConnection(jdbcUrl, getUsername(), getPassword());

以上就是最常用的获取连接的方法。
第一句 Class.forName 方法加载驱动的源码如下,通过反射获取调用的类,然后从调用类的classloader 中获取该驱动类,随后初始化该类(initialize = true)。
在这里插入图片描述
因为在 JDBC 规范中明确要求 Driver (数据库驱动)类必须向 DriverManager 注册自己。所以驱动类在静态代码块中实现主动注册到 DriverManager 的逻辑,这样在初始化该类时就完成驱动的注册了。可参考以下 OracleDriver 有个直观的认知。
在这里插入图片描述

2.实现加载外置驱动jar包

结合常用的使用方法。那根据我的需求,我第一时间想到使用 URLClassLoader 加载相应的 driverClass。然后创建driverClass的实例,并注册到 DriverManager中,最后之间获取Connection就可以实现需求了。实现如下:

	URL[] urls = new URL[]{new URL("file:D:\\drivers\\ojdbc8_19.3.jar)};
	URLClassLoader classLoader = new URLClassLoader(urls);
	Class<?> driverClass = classLoader.loadClass("oracle.jdbc.driver.OracleDriver");
	Driver driver = (Driver) driverClass.getDeclaredConstructor().newInstance();
	DriverManager.registerDriver(driver);
	Connection conn = DriverManager.getConnection(jdbcUrl, getUsername(), getPassword());

这个实现真的可以吗?很多其他博客也是这么写的,但事实上,答案是否定的。如果纯外部引用,这样的写法会报错:

java.sql.SQLException: No suitable driver found for jdbc:oracle:thin:@localhost:1521:ORCL

跟踪到 DriverManager.getConnection 的源码里:
在这里插入图片描述
debug模式发现,虽然 registeredDrivers 里已经存在了我们从外部jar包中加载的驱动实例,但是却在获取连接之前被 isDriverAllowed 方法拒绝了。

在这里插入图片描述
这个方法也很简单,就是从类加载器中加载驱动类。那为什么报错了?!
答案就是这个类加载器的问题,此类加载器是调用 DriverManger.getConnection 方法的类的加载器,那肯定是获取不到的呀!

详细原因:

调用 DriverManger.getConnection 的类的类加载器是默认的 AppClassLoader。
在Java的类加载器双亲委派模型中,在默认情况下,会先委托给父类加载器( ExtClassLoader )来加载类,如果父类加载器无法加载(例如,类不在扩展类库中),委托给启动类加载器(Bootstrap ClassLoader)。只有启动类加载器找不到这个类时,扩展类加载器才会尝试加载。若扩展类加载器也无法加载,最后才会由应用程序类加载器 AppClassLoader 尝试从应用程序的类路径中加载。 但是,AppClassLoader 是负责加载应用程序类路径(包括Maven依赖的类库)中的类。而我们外置的驱动包里的类并不在他的范围内,当然就获取不到了。

我立马就想到了,那用DriverManager获取connection的时候,换成 URLClassLoader不就行了。很遗憾,这个方法没开放。
在这里插入图片描述
以上就是我的一个完整的思考过程。话不多说,最后上正确答案

        URL[] urls = new URL[]{new URL("file:" + oracleConnectionParam.getDriverLocation())};
        URLClassLoader classLoader = new URLClassLoader(urls);
        Class<?> driverClass = classLoader.loadClass(oracleConnectionParam.getDriverClassName());
        Driver driver = (Driver) driverClass.getDeclaredConstructor().newInstance();
        Properties connectionProperties = new Properties();
        connectionProperties.put("user", oracleConnectionParam.getUser());
        connectionProperties.put("password", oracleConnectionParam.getPassword());
        Connection connection = driver.connect(oracleConnectionParam.getJdbcUrl(), connectionProperties);

直接用从 URLClassLoader 中加载的驱动类的 Driver 的实例获取数据库驱动即可。

小结

类加载器
小小的一个数据库连接获取的问题引发我对类加载器的深度思考,值了。不要放过任何一个小问题哦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值