JBoss JDBC驱动报错问题分析与解决

  JBoss JDBC驱动报错问题分析与解决 收藏

 

问题描述:

JBoss使用JDBC驱动时,第一次建立如果库连接会抛出类似于下面的异常

21:21:36,666 WARN  [JBossManagedConnectionPool] Throwable while attempting to get a new connection: null

org.jboss.resource.JBossResourceException: Could not create connection; - nested throwable: (org.jboss.resource.JBossResourceException: Apparently wrong driver class specified for URL: class: oracle.jdbc.driver.OracleDriver, url: jdbc:oracle:thin:@ 10.0.32 .25:1521:sid)

        at org.jboss.resource.adapter.jdbc.local.LocalManagedConnectionFactory.createManagedConnection(LocalManagedConnectionFactory.java:179)

        at org.jboss.resource.connectionmanager.InternalManagedConnectionPool.createConnectionEventListener(InternalManagedConnectionPool.java:565)

但在第二次取得数据库连接时却是正常的。

 

解决方法一:

JDBC驱动拷贝到jboss_server_home/lib目录下。这个方法对于每一个jboss环境都需要进行这样的拷贝,比较麻烦。

 

解决方法二:

webapp打包到一个ear包里,在ear包的META-INF/application.xml文件里添加类似下面的内容:

    <module>

        <java>oracle_jdbc_path/ojdbc14.jar</java>

    </module>

其中的oracle_jdbc_path是相对于ear包的一个路径,以指示驱动程序的位置,通常可以设置成ear中包含的war包的lib目录。这种方法使部署和jboss的环境无关。

对于第二次方法也可以将*-ds.xml文件一起打包到ear里,只要在ear包的META-INF/application.xml添加类似下面的内容就可以了:

    <module>

        <java>oracle-ds.xml</java>

</module>

这样使用数据库连接池的配置也随ear一起发布。

 

问题分析:

出现这个问题的JBoss服务器配置文件jboss_server_home/deploy/jbossweb-tomcat55.sar/META-INF/jboss-service.xml里的UseJBossWebLoader配置项应该都是配置成为false的,即使用的并不是JBoss共享扁平的ClasssLoader并且jboss_server_home/lib不包含jdbc驱动。下面我们来分析造成这个问题的具体原因。

首先我们来了解一下JDBC驱动的管理。JDBC驱动程序在载入的时候都会通过java.sql.DriverManager.registerDriver(Driver)方法将自身注册到驱动管理器中。在注册后我们就可以通过DriverManager.getDriver(String url)方法取得能够处理传入的数据库url的驱动程序,或者通过DriverManager.getConnection(String url, String user, String password)方法取得url对应驱动的连接。但在这里有一个问题需要我们注意,在取得连接或者驱动的时候,它需要从已注册的驱动里选择合适的驱动程序出来。这个合适的驱动总结出来有两条

一是当前调用getDrivergetConnect方法的类的ClassLoader能够载入相应的JDBC驱动程序,并且载入的JDBC驱动程序类要和已注册的驱动程序类相等,调用getDrivergetConnect方法的类ClassLoader通过本地方法DriverManager.getCallerClassLoader()获得,它得到调用类的ClassLoader。这一点理解起来可能不太容易,我们可以简单的举个例子。我们考虑这样的一个ClassLoader结构:

JBoss JDBC驱动报错问题分析与解决

有个SystemClassLoader,它对应到System classpath。还有一个自定义的ClassLoader,如MyClassLoader,它覆盖了ClassLoader默认的parent first委派模型,即先载入自己classpath的类,如果找不到再通过parent ClassLoader来载入。同时我们在system classpath和自定义的ClassLoader对应的classpath里都存在一个JDBC驱动程序的jar包。这个时候,我们用MyClassLoader载入了一个类MyClassMyClass类在system classpath应该不存在的,因此这个类的定义classLoader应该就是MyClassLoader。在调用MyClass的方法前,我们先设置TCLMyClassLoader。在被调用的MyClass方法中,首先注册JDBC驱动,这个时候注册的驱动对应的ClassLoaderMyClassLoader,然后通过ConnectionManager取得数据库连接,ConnectionManager只在system classpath里存在,在ConnectionManager取得数据库连接的时候,它从DriverManager注册的驱动里找,能找到一个MyClassLoader注册的驱动,但由于这个驱动的载入ClassLoaderMyClassLoader,而DriverManager.getConnection方法是在ConnectionManager里调用的,因此ConnectionManagerDriverManager相应方法的调用类,ConnectionManager类的ClassLoaderSystemClassLoaderSystemClassLoader载入的驱动类和MyClass注册的驱动类并不相等,他们由不同的ClassLoader载入。因此MyClass里注册的JDBC驱动不符合要求,不被使用,这个时候SystemClassLoader会重新用SystemClassLoader注册一个JDBC驱动(通过DriverManagrgetCallerClass(callerCL, di.driverClassName)方法来注册),而不使用MyClass注册的驱动。

二是对于符合前面条件的驱动,还需要判断当前这个驱动能不能处理连接url,如果不符合,则从注册的驱动里查找下一次驱动,如果满足,则使用这个驱动来创建连接。

 

接下去我们来分析JBossDataSource管理部分,JBoss会将部署目录里的*-ds.xml转换来MBean serviceSarDeployer进行部署(参见:http://jdeveloper.zhan.cn.yahoo.com/articles/080118/79403_23.html),而SARDeployer在部署这个DataSource的时候是在JBoss全局共享的UCL(JBoss unified ClassLoader)里进行部署的。我们通过JNDI得到JBossDataSource后,会调用DataSource上的getConnection()方法得到连接。JBossDataSource会调用org.jboss.resource.adapter.jdbc.local. LocalManagedConnectionFactory.createManagedConnection()方法来取得连接,这个方法在获取连接前会首先判断自己的一个成员变量driver是否已经初始化过,如果已经初始化过了,则用这个driver来建立数据库连接,如果没有,则通过DriverManager判断已注册的驱动能不能处理连接请求(判断逻辑参考上面的内容),如果不能处理,则用TCL(当前线程ClassLoader,如果在Webapp里调用,这个TCL往往是WebAppClassLoader)来载入JDBC驱动,在载入完后,再判断已注册的驱动能不能处理连接语法请求,如果不能,则用刚才通过TCL初始化的Driver新建一个Driver实例,并赋值给成员变量driver,同时再判断一次已注册的驱动能否处理当前连接请求,如果不能,则抛出JBossResourceException异常,如果可以,则返回driver实例,并用这个实例创建连接。但由于LocalManagedConnectionFactory类是由JBoss全局共享的UCL载入的,它在调用DriverManager上的方法取的数据库驱动时,它会用这个UCL来载入JDBC驱动,这个时候如果UCLclasspath不存在JDBC驱动,使它载入驱动失败,因而导致LocalManagedConnectionFactory判断当前没有可用的驱动可以处理当前请求,而抛出异常。但在第二次进行接连请求的时候,由于LocalManagedConnectionFactory的成员变量driver已被初始化过了,所以不再进行连接请求的能否处理的判断,而使连接可以正常建立。从上面的分析可以看出,要避免这个问题的发生就需要将相应的JDBC驱动放入到这个全局共享的UCL中。

如果将JDBC放入到这个全局共享的UCL中呢,一个方法就是将JDBC driver拷贝到jboss_server_home/lib目录下,这个时候驱动将自动的载入到这个UCL中。其次在ear使用非隔离的classloader的情况下,可以在some.ear/META-INF/application.xml文件里添加类似如下的内容

    <module>

        <java>oracle_jdbc_path/ojdbc14.jar</java>

    </module>

其中的 oracle_jdbc_path 是相对于 ear 包的一个路径,以指示驱动程序的位置,通常可以设置成 ear 中包含的 war 包的 lib 目录。如果 ear classloader 进行的隔离,这样的设置将无效。简单的原因描述就是对 ear 包进行隔离后,声明的 java 模型会将 jar 添加到 ear 隔离后的 classloader 中,而不是添加到 Jboss 全局共享的 UCL 中,如果没有隔离,因为 ear 包用的 ClassLoader 就是那个全局共享的 UCL ,所以也自动的添加到 UCL 中了。

你可能感兴趣的:(jboss)