26

私のデスクトップ アプリケーションでは、新しいデータベースが頻繁に開かれます。Hibernate/JPAを ORM として使用します。問題は、 の作成EntityManagerFactoryが非常に遅く、高速なマシンで約 5 ~ 6 秒かかることです。重量が重いことになっていることは知っていEntityManagerFactoryますが、これは、ユーザーが新しいデータベースをすばやく開くことを期待するデスクトップ アプリケーションには遅すぎます。

  1. インスタンスをより速く取得するために、一部の EntityManagerFactory 機能をオフにすることはできますか? または、作成を高速化するために EntityManagerFactory の一部を遅延して作成することは可能ですか?

  2. データベースの URL を知る前に、どうにかして EntityManagerFactory オブジェクトを作成できますか? これを可能にするために、すべての検証をオフにしていただければ幸いです。

  3. そうすることで、後で使用するために EntityManagerFactory をプールできますか?

  4. EntityManagerFactory をより速く作成する方法はありますか?

詳細情報と JProfiler プロファイリングによる更新

デスクトップ アプリケーションは、保存されたファイルを開くことができます。私たちのアプリケーション ドキュメント ファイル形式は、1 つの SQLite データベースと ZIP ファイル内のいくつかのバイナリ データで構成されています。ドキュメントを開くと、ZIP が抽出され、データベースが Hibernate で開かれます。データベースはすべて同じスキーマを持ちますが、データは明らかに異なります。

初めてファイルを開くときは、次の時間よりもかなり時間がかかるようです。JProfiler で 1 回目と 2 回目の実行をプロファイリングし、結果を比較しました。

1 回目の実行:

create EMF: 4385ms
    build EMF: 3090ms
    EJB3Configuration configure: 900ms
    EJB3Configuration <clinit>: 380ms

calltree1.png.

2 回目の実行:

create EMF: 1275ms
    build EMF: 970ms
    EJB3Configuration configure: 305ms
    EJB3Configuration <clinit>: not visible, probably 0ms

compare_calltree.png.

呼び出しツリーの比較では、いくつかのメソッドが大幅に高速であることがわかります (DatabaseManager. を出発点として):

create EMF: -3120ms
    Hibernate create EMF: -3110ms
        EJB3Configuration configure: -595ms
        EJB3Configuration <clinit>: -380ms
        build EMF: -2120ms
            buildSessionFactory: -1945ms
                secondPassCompile: -425ms
                buildSettings: -346ms
                SessionFactoryImpl.<init>: -1040ms

ホット スポットの比較で興味深い結果が得られました。

スクリーンショットcompare_hotspot.png.

ClassLoader.loadClass: -1686ms
XMLSchemaFactory.newSchema: -184ms
ClassFile.<init>: -109ms

Hibernate クラスのロードなのか、Entity クラスのロードなのかわかりません。

最初の改善点は、必要なすべてのクラスを初期化するためだけにアプリケーションが開始されるとすぐに EMF を作成することです (アプリケーションに既に出荷されているプロトタイプとして空の db ファイルがあります)。@sharakan ご回答ありがとうございます。おそらく、 DeferredConnectionProvider はすでにこの問題の解決策になっているでしょう。

次に DeferredConnectionProvider を試してみます。しかし、それをさらに高速化できる可能性があります。他に提案はありますか?

4

2 に答える 2

12

ConnectionProviderこれは、実際のデコレータとして独自に実装することで実行できるはずですConnectionProvider

ここでの重要な観察はConnectionProvider、が作成されるまでは使用されないということです(警告EntityManagerについてはコメントを参照してください)。supportsAggressiveRelease()したがって、DeferredConnectionProviderクラスを作成し、それを使用してを構築できますEntityManagerFactoryが、ユーザー入力を待ち、実際にEntityManagerインスタンスを作成する前に遅延初期化を実行します。私はこれをラッパーとして書いていますが、他の実装をベースとしてConnectionPoolImpl使用できるはずです。ConnectionProvider

public class DeferredConnectionProvider implements ConnectionProvider {

    private Properties configuredProps;
    private ConnectionProviderImpl realConnectionProvider;

    @Override
    public void configure(Properties props) throws HibernateException {
        configuredProps = props;
    }

    public void finalConfiguration(String jdbcUrl, String userName, String password) {
        configuredProps.setProperty(Environment.URL, jdbcUrl);
        configuredProps.setProperty(Environment.USER, userName);
        configuredProps.setProperty(Environment.PASS, password);

        realConnectionProvider = new ConnectionProviderImpl();
        realConnectionProvider.configure(configuredProps);
    }

    private void assertConfigured() {
        if (realConnectionProvider == null) {
            throw new IllegalStateException("Not configured yet!");
        }
    }        

    @Override
    public Connection getConnection() throws SQLException {
        assertConfigured();

        return realConnectionProvider.getConnection();
    }

    @Override
    public void closeConnection(Connection conn) throws SQLException {
        assertConfigured();

        realConnectionProvider.closeConnection(conn);
    }

    @Override
    public void close() throws HibernateException {
        assertConfigured();

        realConnectionProvider.close();
    }

    @Override
    public boolean supportsAggressiveRelease() {
        // This gets called during EntityManagerFactory construction, but it's 
        // just a flag so you should be able to either do this, or return
        // true/false depending on the actual provider.
        return new ConnectionProviderImpl().supportsAggressiveRelease();
    }
}

それを使用する方法の大まかな例:

    // Get an EntityManagerFactory with the following property set:
    //     properties.put(Environment.CONNECTION_PROVIDER, DeferredConnectionProvider.class.getName());
    HibernateEntityManagerFactory factory = (HibernateEntityManagerFactory) entityManagerFactory;

    // ...do user input of connection info...

    SessionFactoryImpl sessionFactory = (SessionFactoryImpl) factory.getSessionFactory();
    DeferredConnectionProvider connectionProvider = (DeferredConnectionProvider) sessionFactory.getSettings()
                    .getConnectionProvider();

    connectionProvider.finalConfiguration(jdbcUrl, userName, password);

の初期設定をEntityManagerFactory別のスレッドなどに配置して、ユーザーがそれを待つ必要がないようにすることができます。次に、接続情報を指定した後、彼らが待つのは接続プールの設定だけです。これは、オブジェクトモデルの解析に比べてかなり速いはずです。

于 2013-03-13T17:54:21.463 に答える
3

インスタンスをより速く取得するために、一部の EntityManagerFactory 機能をオフにすることはできますか?

そう信じないでください。EMF には、JDBC 接続/プールの初期化以外にあまり多くの機能はありません。

または、作成を高速化するために EntityManagerFactory の一部を遅延して作成することは可能ですか?

EMF を怠惰に作成するのではなく、ユーザーがパフォーマンスの低下に気付いたときに、反対の方向に向かうことをお勧めします。ユーザーが実際に必要とする前に、事前に EMF を作成してください。アプリケーションの初期化中に(または少なくともデータベースについて知ったらすぐに)、おそらく別のスレッドで、事前に一度作成します。アプリケーション/データベースが存在する限り、それを再利用してください。

データベースの URL を知る前に、どうにかして EntityManagerFactory オブジェクトを作成できますか?

いいえ - JDBC 接続を作成します。

より適切な質問は、アプリケーションがデータベース接続 URL を動的に検出するのはなぜですか? ということだと思います。データベースはオンザフライで作成/利用可能であり、接続パラメーターを事前に予測する方法はないと言っていますか。それは本当に避けるべきです。

そうすることで、後で使用するために EntityManagerFactory をプールできますか?

いいえ、EMF をプールすることはできません。プールできるのは接続です。

EntityManagerFactory をより速く作成する方法はありますか?

同意します - EMF の初期化には 6 秒では遅すぎます。

JPA/JDBC/JVM よりも、選択したデータベース テクノロジに関係していると思われます。私の推測では、接続時にデータベースが初期化されている可能性があります。アクセスを使用していますか? どのDBを使用していますか?

リモートにあるデータベースに接続していますか? WAN経由ですか?ネットワークの速度/遅延は良好ですか?

クライアント PC のパフォーマンスは制限されていますか?

編集:コメントの後に追加

独自の ConnectionProvider を実際の ConnectionProvider の周りにデコレーターとして実装しても、ユーザー エクスペリエンスはまったく高速化されません。データベース インスタンスを初期化する必要があり、EMF と EM を作成し、その後 JDBC 接続を確立する必要があります。

オプション:

  1. 共通のプリロードされた DB インスタンスを共有する: あなたのビジネス シナリオでは不可能のようです (ただし、JSE テクノロジはこれをサポートし、クライアント サーバー設計もサポートします)。
  2. 起動が高速な DB に変更する: Derby (別名 Java DB) は最新の JVM に含まれており、起動時間は約 1.5 秒 (コールド) および 0.7 秒 (ウォーム - データ プリロード) です。
  3. 多くの (ほとんどの?) シナリオでは、JAXB と STAX を使用してメモリ内の Java オブジェクトにデータを直接ロードするのが最速の解決策です。その後、メモリ内にキャッシュされたデータを使用します (特に、マップ、ハッシュ、配列リストなどのスマート構造を使用)。JPA が POJO クラスをデータベースのテーブルと列にマップできるように、JAXB は POJO クラスを XML スキーマにマップし、XML doc インスタンスを操作できます。複数の結合と DB インデックスの強力な使用を伴う SQL セットベースのロジックを使用する非常に複雑なクエリがある場合、これはあまり望ましくありません。

(2) 限られた労力でおそらく最良の改善が得られるでしょう。
さらに: - アプリの使用中ではなく、展開中にデータ ファイルを解凍してみてください。- UI の起動と並行して実行される起動スレッドで EMF を初期化します - アプリの最初のステップの 1 つとして DB の初期化を開始してみてください (つまり、JDBC を使用して実際のインスタンスに接続することを意味します)。

于 2013-03-14T05:29:57.387 に答える