2

クライアントサーバーモデルに適合するEclipseプラグインを開発しています。これは商用プロジェクトであるため、プラグインでサポートするさまざまなデータベースのJDBCドライバーを再配布することはできません。

そこで、ユーザーがjarを見つけて、jarファイル内のクラスを反復処理し、各クラスをロードしてjava.sql.Driverインターフェースを実装していることを確認する簡単な検出メカニズムを使用できるように、設定ページを開発しました。これはすべてうまくいきます。

しかし、問題は、私がHibernateを使用していることです。また、HibernateはClass.forName()を使用してJDBCドライバーをインスタンス化します。

以下を使おうとするとClassNotFoundException

public Object execute(final IRepositoryCallback callback)
{
    final DatabaseDriverClassLoader loader = new DatabaseDriverClassLoader(
        Activator.getDefault().getDatabaseDriverRegistry());
    final ClassLoader oldLoader = Thread.currentThread()
        .getContextClassLoader();
    try
    {
        Thread.currentThread().setContextClassLoader(loader);
        try
        {
            final SessionFactory sessionFactory = this.configuration
                .buildSessionFactory();
            if (sessionFactory != null)
            {
                final Session session = sessionFactory
                    .openSession();
                if (session != null)
                {
                    // CHECKSTYLE:OFF
                    try
                    // CHECKSTYLE:ON
                    {
                        return callback.doExecute(session);
                    }
                    finally
                    {
                        session.close();
                    }
                }
            }
            connection.close();
        }
        finally
        {
        }
    }
    // CHECKSTYLE:OFF
    catch (Exception e)
    // CHECKSTYLE:ON
    {
        RepositoryTemplate.LOG.error(e.getMessage(), e);
    }
    finally
    {
        Thread.currentThread().setContextClassLoader(oldLoader);
    }
    return null;
}

そして、次のように自分でドライバーを作成しようとすると、SecurityExceptionが発生します。

public Object execute(final IRepositoryCallback callback)
{
    final DatabaseDriverClassLoader loader = new DatabaseDriverClassLoader(
        Activator.getDefault().getDatabaseDriverRegistry());
    final ClassLoader oldLoader = Thread.currentThread()
        .getContextClassLoader();
    try
    {
        Thread.currentThread().setContextClassLoader(loader);
        final Class driverClass = loader.loadClass(this.connectionDriverClassName);
        final Driver driver = (Driver)driverClass.newInstance();
        DriverManager.registerDriver(driver);
        try
        {
            final Connection connection = DriverManager.getConnection(
                this.connectionUrl, this.connectionUsername,
                this.connectionPassword);
            final SessionFactory sessionFactory = this.configuration
                .buildSessionFactory();
            if (sessionFactory != null)
            {
                final Session session = sessionFactory
                    .openSession(connection);
                if (session != null)
                {
                    // CHECKSTYLE:OFF
                    try
                    // CHECKSTYLE:ON
                    {
                        return callback.doExecute(session);
                    }
                    finally
                    {
                        session.close();
                    }
                }
            }
            connection.close();
        }
        finally
        {
            DriverManager.deregisterDriver(driver);
        }
    }
    // CHECKSTYLE:OFF
    catch (Exception e)
    // CHECKSTYLE:ON
    {
        RepositoryTemplate.LOG.error(e.getMessage(), e);
    }
    finally
    {
        Thread.currentThread().setContextClassLoader(oldLoader);
    }
    return null;
}

編集:それが最良のオプションかどうかはわかりませんが、ConnectionProviderを使用してドライバーをインスタンス化できる独自の実装方法を採用し、の代わりにClass.forName()を使用して接続を開きます。かなり基本的ですが、特定のユースケースでは接続プールは必要ありません。Driver.connect()DriverManager.getConnection()

方法は次のconfigure()とおりです。

public void configure(final Properties props)
{
    this.url = props.getProperty(Environment.URL);
    this.connectionProperties = ConnectionProviderFactory
        .getConnectionProperties(props);

    final DatabaseDriverClassLoader classLoader = new DatabaseDriverClassLoader(
        Activator.getDefault().getDatabaseDriverRegistry());

    final String driverClassName = props.getProperty(Environment.DRIVER);
    try
    {
        final Class driverClass = Class.forName(driverClassName, true,
            classLoader);
        this.driver = (Driver)driverClass.newInstance();
    }
    catch (ClassNotFoundException e)
    {
        throw new HibernateException(e);
    }
    catch (IllegalAccessException e)
    {
        throw new HibernateException(e);
    }
    catch (InstantiationException e)
    {
        throw new HibernateException(e);
    }
}

そして、そのgetConnection()方法は次のとおりです。

public Connection getConnection()
    throws SQLException
{
    return this.driver.connect(this.url, this.connectionProperties);
}
4

1 に答える 1

4

Class.forName()OSGi では大きな問題です。これは実際には誰のせいでもありません。どちらも、相手のクライアントが期待する方法で動作しないクラス ローダーを使用するだけです (つまり、OSGi クラス ローダーは hibernate が期待する方法で動作しません)。

いくつかの方法のいずれかを実行できると思いますが、現在考えられる方法は次のとおりです。

  • JDBC ドライバーを OSGi バンドルとしてパッケージ化するクリーンな方法です。クラスをサービスとして提供します。宣言型サービスを使用してこれを行うことができます (おそらくより優れています)。または、開始を管理する必要があるアクティベーターを作成します。ドライバーを取得する準備ができたら、JDBCDriver サービスを取得し、関心のあるクラスを探します。
  • きれいではない方法ですが、最初の方法よりも少ない労力でDynamicImport-Package機能します。バンドルされたドライバーからエクスポートされたパッケージを追加するために使用します。このように、クライアント コードは使用するクラスを認識できますが、実行時までそのことを知る必要はありません。ただし、すべてのケースをカバーするには、パッケージ パターンを試してみる必要があるかもしれません (そのため、あまりクリーンではありません)。
  • OSGi の方法が少ない。これは、ドライバーを Eclipse クラスパスに追加し、アプリケーションの親クラスローダーを追加することです。これを追加できます: osgi.parentClassloader=appconfig.ini に。これは、特にファイルを制御できない場合は、展開に合わない可能性がありconfig.iniます。
  • OSGi 以外の方法では、コンテキスト クラス ローダーを使用する代わりに、URLClassLoader. これは、ドライバー jar でいっぱいのディレクトリがある場合、またはユーザーがドライバー jar の場所を直接的または間接的に指定できる場合にのみ機能します。
于 2008-12-18T20:10:22.363 に答える