一个程序使用相同数据库的两个不同版本的jar包

时间:2019-11-14 15:29:16   热度:37.1℃   作者:网络

原标题:一个程序使用相同数据库的两个不同版本的jar包

问题场景

现在很多工程为了功能扩展,都给出了插件化的方式。只需要用户配置好配置文件,提供好需要的jar包,就能完成响应功能。本文说一下,数据转存数的功能实现。现在项目一般都离不开数据库,自己本身的项目就会带这驱动包,但是也会有这样的一种需求,就是数据额外存储的定制化,当产生的数据在自己项目的流程中不满足现在使用。例如做报表,项目本身产生数据,但是需要把里面的一部分数据拿出来和其他文本数据结合,产生新的数据。或者现有的数据进行时间的整合,直接变成周数据,月数据,年数据。这些都是需要根据不同用户自己设定的。这些数据往往是需要另外的数据库的,这就带来了一个问题,项目本身有一个jar包,例如Mysql4.5的,客户想组织信息存入mysql5.5,jar包的类是相同的,这样就带来了驱动加载的问题,因为类的相同的,而且驱动不是完全不兼容,而是在使用上会出问题。典型的就是ojdbc14,用这个版本的驱动建立数据库使用语句池缓存会有问题,ojdbc5,6就没事。(毕竟语句池缓存能带来性能上优化,不能说为了兼容驱动放弃性能,而且其他潜在的问题还没有暴露)。

问题分析

本质上就是一个获取正确连接的问题,我们正常使用驱动第一件事就是class.forname(xxxxx),mysql(注册肯定是需要注册的,但是不一定发生在class.forname上)的话就是把驱动类注册到了DriverManager上,然后我们通过DriverManager来获取connection对象。getConnection的过程可以分为以下步骤。

1,验证对象

private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {

boolean result = false;

if(driver != null) {

Class aClass = null;

try {

aClass = Class.forName(driver.getClass().getName(), true, classLoader);

} catch (Exception ex) {

result = false;

}

result = ( aClass == driver.getClass() ) ? true : false;

}

return result;

}

这里的classloader的调用getConnection的对象的classloader,driver则是驱动注册到DriverManager的对象,这里通过classloader再次加载driver类,这里如果加载到的class和driver是一样的,当前的classloader要不是加载驱动的classloader,要不就是其子classloader,以此保持不会出现类A非类A的问题(classloader的经典问题)。

2,获取连接

for(DriverInfo aDriver : registeredDrivers) {

if(isDriverAllowed(aDriver.driver, callerCL)) {

try {

println( trying + aDriver.driver.getClass().getName());

Connection con = aDriver.driver.connect(url, info);

if (con != null) {

println(getConnection returning + aDriver.driver.getClass().getName());

return (con);

}

} catch (SQLException ex) {

if (reason == null) {

reason = ex;

}

}

} else {

println( skipping: + aDriver.getClass().getName());

}

}

这里就是获取连接的过程,registeredDrivers就是已经注册的驱动,它是一个集合,下面是声明。

private final static CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList();

这样可以看到,只要通过检验就获取连接,获取成功就返回,这就是我们获取的connection对象。这里的一个明显问题就是集合是一个线程安全的链表,谁先被加载谁先去获取connection对象。如果按照以往的方式,这里有两个难题,一个是classloader的验证,一个是遍历的顺序。看起来只要让classloader验证正确,那么就可以保证遍历的的时候正确的loader加载正确的驱动了。很明显jdk不让你这么玩,因为这个方法是private的,这就必须保证你调用的classloader和注册的classloader必须是同一个,注册的还简单,但是调用的classloader也这样,那就必须把代码实现打包出来,这样很不利于我们管理。

以上我们看通了java的连接流程,那么我们尝试做精简,提取关键代码。

Connection con = aDriver.driver.connect(url, info);

其实获取连接就这样一句话,剩下的内容都是为了保证这句话正确执行的校验,现在我们自己来保证这个校验,这样就把关键逻辑简化成获取Driver对象,获取Connection对象,2个过程。无论怎么思考,都绕不过classloader,所以自定义classloader是肯定绕不过去了。我们借助classloader来保证加载的驱动类是正确的,这点就比较容易的实现了。

实现

首先需要一个违背双亲委托的classloader,不知道怎么写的可以查看我上一篇文章。地址:https://my.oschina.net/xpbob/blog/761436

这种classloader很好的保证了我们加载的安全性,因为地址是我们指定的,用户扔进去多个相同版本那和在was的lib目录下扔入效果一样,都不保证正常运行。简单写一个使用方式。

public static void main(String[] args) throws Exception {

// TODO Auto-generated method stub

String url =jdbc:mysql://127.0.0.1/test;

String user=root;

String password =;

String driverClass=com.mysql.jdbc.Driver;

File file = new File(c:/mysql-connector-java-5.1.8.jar);

MyClassloader loader = new MyClassloader(new URL[]{file.toURL()});

Driver driver = (Driver) loader.loadClass(driverClass).newInstance();

java.util.Properties info = new java.util.Properties();

if (user != null) {

info.put(user, user);

}

if (password != null) {

info.put(password, password);

}

Connection connect = driver.connect(url, info);

if(connect!=null){

System.out.println(success);

}else{

System.out.println(error);

}

}

上面的url,username等等信息都是连接数据库必要的信息,可以写死也可以指定,然后那个file路径是我指定的地址,在c盘,根据项目来设定。这里直接生成driver对象,此处就是com.mysql.jdbc.Driver,这里就体现到面向接口编程的好处了,Driver类是jdk的,只要保证路径就都能加载到,不会出现冲突。运行代码,显示success就表示你成功获取connection对象,剩下就是操作数据库语句的逻辑了。

上一篇: 原创幽默笑话:一年前我去外地出差,半夜遇...

下一篇: 绝杀未果!奥运预选赛中国女篮80:81憾...


 本站广告