- PR -

異なるバージョンのJDBCドライバの同時使用

投稿者投稿内容
カーニー
ぬし
会議室デビュー日: 2003/09/04
投稿数: 358
お住まい・勤務地: 東京
投稿日時: 2004-12-28 00:47
返答が前後しちまいました。

sa2sa2さん、どうもありがとうございます。
でも残念ながらだめなんですよ。DB管理ソフトの都合で、業務DBの構成を変更するってのは。本末転倒の感もありまして。

でも、いろんなアイディアが出てくるもんですね。
このスレッドが後々、どなたかのお役に立てばうれしいですね。
かつのり
ぬし
会議室デビュー日: 2004/03/18
投稿数: 2015
お住まい・勤務地: 札幌
投稿日時: 2004-12-28 00:58
Class#forName(String name, boolean initialize, ClassLoader loader)
を使って、ドライバマネージャの初期化を行って、
そのドライバマネージャでコネクションを取得すれば
よいのではと思ったのですが。。。

URLClassLoaderをドライバ(jar)分用意して、
マニフェストとURLClassLoaderをキーにするなり、
設定ファイル等の内容をキーにするなりして、
マップでURLClassLoaderを管理するのがよいかなと思います。

WEBアプリを分けるというのも、Tomcatなら各コンテキストごとに
それぞれクラスローダが存在しています。
別個のアプリにしても、1つのアプリで複数のクラスローダを使用するのも
結果的には変わらないのではないでしょうか。

手元に試せる環境がないので、嘘言ってたらホントにすいません。
カーニー
ぬし
会議室デビュー日: 2003/09/04
投稿数: 358
お住まい・勤務地: 東京
投稿日時: 2004-12-28 14:49
かつのりさん、たびたびありがとうございます。
なんかできそうな気がしてきました。

検証は年明けになりますが、Oracle7と9.2の同時対応はどうしても実現したいので、時間がかかっても絶対やります。そして報告します。

それではまた。
カーニー
ぬし
会議室デビュー日: 2003/09/04
投稿数: 358
お住まい・勤務地: 東京
投稿日時: 2005-01-18 21:01
ようやく時間がとれて検証が終わりました。
結果から述べると大成功でした。

必要なステップは概ね以下の通りです。

1)クラスローダを作成
目的のJDBCドライババージョンのJARファイルのみから、クラスをロードするようなものを作ります。
実際には"org.apache.catalina.loader.WebappClassLoader"をパクりました。
パクり方は後ほど。

2)JDBCドライバのロード
1で作ったクラスローダを使って、
 Class#forName(String name, boolean initialize, ClassLoader loader)
を起動すればよし。

3)ドライバを取得する
DriverManagerにはクセがあります。そのクセを知った上でDriverManagerを活用する方法と、いっそDriverManagerを全く使わない方法の2つがあります。

これも詳細は後ほど。
カーニー
ぬし
会議室デビュー日: 2003/09/04
投稿数: 358
お住まい・勤務地: 東京
投稿日時: 2005-01-18 21:20
クラスローダの作り方です。

http://www.atmarkit.co.jp/fjava/javatips/049jspservlet025.html でも説明されている階層構造に合わせ、"WebAppX"の下にそのクラスローダを置きたかったので、"WebAppX"と同じクラスを使うことにしました。(実体は "org.apache.catalina.loader.WebappClassLoader")

java.net.URLClassLoaderを使うと、クラス検索を先に親クラスローダに委譲してしまうので、WebappClassLoaderを使わないと都合が悪いのです。
(WebappClassLoaderはまずローカルを検索して、なければ親に委譲)

しかしWebappClassLoaderは $CATALINA_HOME/server/lib/catalina.jar の中で定義されていてWebアプリからは見えないので、以下のようにリフレクションを使いました。

コード:
        // WebAppXクラスローダの取得
        ClassLoader parent = this.getClass().getClassLoader();
        Class clClass = parent.getClass();

        // WebAppXの下にクラスローダを作成
        Class argsClasses[] = {Class.forName("java.lang.ClassLoader")};
        Constructor constructor = clClass.getConstructor(argsClasses);
        Object argsObjects[] = {parent};
        URLClassLoader classLoader = (URLClassLoader)constructor.newInstance(argsObjects);
        
        // JDBCドライバのJARファイルのパスをセット
        Class argClasses[] = {Class.forName("java.lang.String")};
        Method method = clClass.getMethod("addRepository", argClasses);
        Object argObjects[] = {"file:/opt/oracle/.../ojdbc14.jar"};
        method.invoke(classLoader, argObjects);

        // クラスローダを開始(WebappClassLoaderはこうしないといけないみたい)
        method = clClass.getMethod("start", null);
        method.invoke(classLoader, null);



ちなみにクラスローダの階層構造にこだわらなければ、単にjava.net.URLClassLoaderを使ってもよさそうなんですが、検証してません。

カーニー
ぬし
会議室デビュー日: 2003/09/04
投稿数: 358
お住まい・勤務地: 東京
投稿日時: 2005-01-18 21:35
クラスローダを新しく作ったら、そのそれぞれでDriverManagerをロードしてあげないといけない、と最初は考えていました。WebappClassLoaderも、J2SE標準クラスの検索だけは最初っから親クラスローダに委譲してしまうので、これは厳しい!

ところが、SunのJ2SEのDriverManagerはgetConnection()がコールされると、コール元のオブジェクトのクラスローダを判別して、それと同じクラスローダがロードしたドライバしか返さない仕様であることが判明。

つまり、クラスローダを作ったら何か適当なオブジェクトを生成して、その中で Class#forName(String name, boolean initialize, ClassLoader loader) とか、DriverManager.getConnection() してあげるとよいわけです。

しかし今回は既に存在するアプリの構造上、それが困難だったので、いっそDriverManagerに頼るのはやめることにしました。どういうことかと言うと、こういうことです。

コード:
        Class driverClass = Class.forName("oracle.jdbc.driver.OracleDriver", true, classLoader);
        Driver driver = (Driver)driverClass.newInstance();

        Properties info = new java.util.Properties();
        info.put("user", "DBユーザ名");
        info.put("password", "パスワード");
        
    	Connection conn = driver.connect(url, info);



以上のような感じでもって、同じ名前のDriverクラスの複数バージョン使い分け、をめでたく達成することができました。

色々とアイディアを提供してくれた皆さん、改めてお礼を申し上げます。
ご協力どうもありがとうございました。

スキルアップ/キャリアアップ(JOB@IT)