22

Hibernate を (JPA 経由で) 使用する、長時間実行される (しかし単純な) アプリケーションがあります。実行中にかなり劇的な減速が発生しました。entityManager.clear()時折の電話を必要とするように絞り込むことができました。Hibernate のエンティティ マネージャーが 100,000 個のエンティティを追跡している場合、数個しか追跡していない場合よりも最大 100 倍遅くなります (以下の結果を参照)。 私の質問は、Hiberate が多くのエンティティを追跡しているときになぜそんなに遅くなるのかということです。それを回避する他の方法はありますか?


!!! 更新: これを Hibernate の自動フラッシュ コードに絞り込むことができました。!!!

具体的にはorg.hibernate.event.internal.AbstractFlushingEventListenerflushEntities()メソッド (少なくとも Hibernate 4.1.1.Final では)。その中には、永続化コンテキスト内のすべてのエンティティを反復処理するループがあり、それぞれのフラッシュに関する広範なチェックを実行します (私の例ではすべてのエンティティが既にフラッシュされていますが!)。

私の質問の2番目の部分に部分的に答えるとFlushModeType.COMMIT、クエリでフラッシュモードを設定することでパフォーマンスの問題に対処できます(以下の更新された結果を参照)。例えば

Place place = em.createQuery("from Place where name = :name", Place.class)
    .setParameter("name", name)
    .setFlushMode(FlushModeType.COMMIT)  // <-- yay!
    .getSingleResult();

...しかし、これはかなり醜い解決策のように思えます-物事がフラッシュされたかどうかを知る責任を、更新メソッドに保持するのではなく、クエリメソッドに渡します。また、すべてのクエリ メソッドでフラッシュ モードを COMMIT に設定するか、EntityManager で設定する必要があることもほぼ意味します。

これは私を不思議に思います:これは予想される動作ですか? フラッシュやエンティティの定義方法に何か問題がありますか? それとも、これは Hibernate の制限 (またはおそらくバグ) ですか?


問題を特定するために使用したサンプル コードは次のとおりです。

テストエンティティ

@Entity @Table(name="place") @Immutable
public class Place {
    private Long _id;
    private String _name;

    @Id @GeneratedValue
    public Long getId() { return _id; }
    public void setId(Long id) { _id = id; }

    @Basic(optional=false) @Column(name="name", length=700,
        updatable=false, nullable=false, unique=true,
        columnDefinition="varchar(700) character set 'ascii' not null")
    public String getName() { return _name; }
    public void setName(String name) { _name = name; }

    @Override
    public boolean equals(Object o) { /* ... */ }

    @Override
    public int hashCode() { return getName().hashCode(); }
}

ベンチマーク コード

私が持っているテスト コードは、100000 個のランダムな地名を生成して挿入します。次に、そのうちの 5000 個を名前でランダムにクエリします。名前の欄に索引があります。

Place place = em.createQuery(
    "select p from Place p where p.name = :name", Place.class)
    .setParameter("name", name)
    .getSingleResult();

比較のために、またそれがデータベース内のものではないことを確認するために、次の JDBC ベースのクエリ ( の下em.unwrap(Session.class).doWork(...)) を別のランダムに選択された 5000 の地名に対して実行しました。

PreparedStatement ps = c.prepareStatement(
    "select id, name from place where name = ?");
ps.setString(1, name);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
    Place place = new Place();
    place.setId(rs.getLong(1));
    place.setName(rs.getString(2));
}
rs.close();
ps.close();

(注: ベンチマーク用の 5000 クエリごとに PreparedStatement を作成して閉じます)。

結果

以下のすべての結果は、5000 クエリの平均です。JVMが与えられました-Xmx1G

Seconds/Query    Approach
0.000160s        JDBC
0.000286s        Hibernate calling clear() after import and every 100 queries
0.000653s        Hibernate calling clear() once after the import
0.012533s        Hibernate w/o calling clear() at all
0.000292s        Hibernate w/o calling clear(), and with flush-mode COMMIT

その他の観察: Hibernate クエリ中 (クリア コー​​ルなし)、Java プロセスはコアを 100% 近くの使用率に固定しました。JVM が 500MB ヒープを超えたことはありません。クエリ中にも多くの GC アクティビティがありましたが、CPU 使用率は明らかに Hibernate コードによって支配されていました。

4

2 に答える 2

8

おそらくEntityManager、永続オブジェクト (つまり、 を呼び出して作成されたオブジェクト) を追跡する方法に慣れているでしょうem.createQuery(...).getSingleResult()。それらは、いわゆる永続的なコンテキストまたはセッション(Hibernate 用語) に蓄積され、非常に優れ機能を実現します。たとえば、mutator メソッドsetName(...)を呼び出してオブジェクトを変更すると、EntityManager必要に応じてメモリ内のこの状態変更がデータベースと同期されます (UPDATE ステートメントが発行されます)。save()これは、明示的なメソッドまたはメソッドを呼び出す必要なく発生しますupdate()。必要なのは、通常の Java オブジェクトであるかのようにオブジェクトを操作することだけでありEntityManager、永続性を処理します。

なぜこれは遅いのですか?

1つには、メモリ内の主キーごとに単一のインスタンスが 1 つだけ存在することが保証されます。これは、1 つの同じ行を 2 回ロードすると、ヒープ内に作成されるオブジェクトが 1 つだけになることを意味します (両方の結果は になります==)。これは非常に理にかなっています。同じ行のコピーが 2 つある場合EntityManager、両方のオブジェクトを個別に変更できるため、Java オブジェクトを確実に同期することを保証できないと想像してください。Entitymanagerおそらく、追跡する必要のあるオブジェクトが多数ある場合、最終的に遅くなる低レベルの操作が他にもたくさんあります。これらのclear()メソッドは実際にオブジェクトを永続コンテキストから削除し、タスクをより簡単にします (追跡するオブジェクトが少ない = 操作が速くなります)。

どうすれば回避できますか?

EntityManager実装が Hibernate の場合、これらのパフォーマンス ペナルティを回避するように設計されたStatelessSessionを使用できます。私はあなたがそれを乗り越えることができると思います:

StatelessSession session = ((Session) entityManager.getDelegate()).getSessionFactory().openStatelessSession();

(NB! テストされていないコード、別の質問から取得)

于 2012-04-13T16:22:38.653 に答える