web-dev-qa-db-ja.com

Hibernateでデタッチされたオブジェクトを再接続する適切な方法は何ですか?

同じIDのオブジェクトが既にセッションに存在している可能性がありますが、エラーが発生する可能性がありますが、デタッチされたオブジェクトを休止状態セッションに再アタッチする必要がある状況があります。

今、私は2つのことの1つをすることができます。

  1. getHibernateTemplate().update( obj )これは、hibernateセッションにオブジェクトがまだ存在しない場合にのみ機能します。後で必要になったときに、指定された識別子を持つオブジェクトがセッションに既に存在することを示す例外がスローされます。

  2. getHibernateTemplate().merge( obj )これは、オブジェクトがhibernateセッションに存在する場合にのみ機能します。後でこれを使用する場合、オブジェクトをセッションに入れる必要があるときに例外がスローされます。

これら2つのシナリオを考えると、セッションをオブジェクトに一般的にアタッチするにはどうすればよいですか?よりエレガントな解決策が必要なので、例外を使用してこの問題の解決策の流れを制御したくありません...

177
Stefan Kendall

そのため、JPAで古いデタッチされたエンティティを再アタッチする方法はないようです。

merge()は、古い状態をDBにプッシュし、介在する更新を上書きします。

refresh()は、分離されたエンティティでは呼び出せません。

lock()は、デタッチされたエンティティで呼び出すことはできません。できたとしても、エンティティを再アタッチしました。引数 'LockMode.NONE'で 'lock'を呼び出し、ロックしているがロックしていないことを意味するAPIは最も直感的ではありません私が今まで見たデザイン。

あなたは立ち往生しています。 detach()メソッドはありますが、attach()またはreattach()はありません。オブジェクトのライフサイクルの明らかなステップは利用できません。

JPAに関する同様の質問の数から判断すると、JPAが首尾一貫したモデルを持っていると主張する場合でも、取得方法を理解しようとして多くの時間を浪費することを呪われているほとんどのプログラマーのメンタルモデルとは確実に一致しないようですJPAは最も簡単なことを行い、アプリケーション全体でキャッシュ管理コードを作成します。

それを行う唯一の方法は、古いデタッチされたエンティティを破棄し、L2またはDBにヒットする同じIDで検索クエリを実行することです。

ミク

172
mikhailfranco

これらの答えはすべて、重要な区別を見逃しています。 update()は、オブジェクトグラフをセッションに(再)接続するために使用されます。渡すオブジェクトは、管理対象になります。

merge()は実際には(再)接続APIではありません。 merge()には戻り値があることに注意してください。これは、管理グラフを返すためです。これは、渡したグラフではない場合があります。 merge()はJPA APIであり、その動作はJPA仕様によって管理されます。 merge()に渡すオブジェクトがすでに管理されている(すでにセッションに関連付けられている)場合、それはHibernateが動作するグラフです。渡されるオブジェクトは、merge()から返されるオブジェクトと同じです。ただし、me​​rge()に渡すオブジェクトがデタッチされると、Hibernateは管理される新しいオブジェクトグラフを作成し、デタッチされたグラフの状態を新しいマネージグラフにコピーします。繰り返しになりますが、これはすべてJPA仕様によって指示および管理されます。

「このエンティティが管理されていることを確認する、または管理する」ための一般的な戦略に関しては、まだ挿入されていないデータも考慮するかどうかによって異なります。あなたがそうだと仮定して、次のようなものを使用してください

if ( session.contains( myEntity ) ) {
    // nothing to do... myEntity is already associated with the session
}
else {
    session.saveOrUpdate( myEntity );
}

Update()ではなくsaveOrUpdate()を使用したことに注意してください。ここでまだ挿入されていないデータを処理したくない場合は、代わりにupdate()を使用してください...

25
Steve Ebersole

ndiplomatic answer:おそらく、拡張された永続コンテキストを探しています。これが Seam Framework ...の背後にある主な理由の1つです。特にSpringでHibernateを使用するのに苦労している場合は、Seamのドキュメントの this piece をチェックしてください。

外交的回答:これは Hibernate docs で説明されています。さらに説明が必要な場合は、 Java Persistence with Hibernate のセクション9.3.2をご覧ください。 strongly HibernateでCRUD以外のことをしている場合は、この本を入手することをお勧めします。

20
cwash

エンティティが変更されていないことが確かな場合(または変更が失われることに同意する場合)、ロック付きでセッションに再アタッチできます。

session.lock(entity, LockMode.NONE);

何もロックしませんが、セッションキャッシュからエンティティを取得するか、(そこに見つからない場合)DBから読み取ります。

「古い」(たとえばHttpSessionから)エンティティからリレーションをナビゲートしているときにLazyInitExceptionを防ぐことは非常に便利です。最初にエンティティを「再接続」します。

Getの使用も機能しますが、継承をマップする場合を除きます(getId()で既に例外をスローします)。

entity = session.get(entity.getClass(), entity.getId());
12
John Rizzo

org.hibernate.SessionのJavaDocに戻り、以下を見つけました。

一時インスタンスは、save()persist()、またはsaveOrUpdate()を呼び出すことで永続化できます。永続インスタンスは、delete()を呼び出すことで一時的にできます。 get()またはload()メソッドによって返されるインスタンスはすべて永続的です。分離されたインスタンスは、update()saveOrUpdate()lock()、またはreplicate()を呼び出すことで永続化できます。 merge()を呼び出すことにより、一時インスタンスまたは切り離されたインスタンスの状態を新しい永続インスタンスとして永続化することもできます。

したがって、update()saveOrUpdate()lock()replicate()、およびmerge()が候補オプションです。

update():同じ識別子を持つ永続インスタンスがある場合、例外をスローします。

saveOrUpdate():保存または更新

lock():非推奨

replicate():現在の識別子の値を再利用して、指定された分離されたインスタンスの状態を保持します。

merge():同じ識別子を持つ永続オブジェクトを返します。指定されたインスタンスは、セッションに関連付けられなくなります。

したがって、lock()はすぐに使用すべきではなく、機能要件に基づいて1つ以上を選択できます。

9
Amitabha Roy

NHibernateを使用してC#でその方法で実行しましたが、Javaでも同じように動作するはずです。

public virtual void Attach()
{
    if (!HibernateSessionManager.Instance.GetSession().Contains(this))
    {
        ISession session = HibernateSessionManager.Instance.GetSession();
        using (ITransaction t = session.BeginTransaction())
        {
            session.Lock(this, NHibernate.LockMode.None);
            t.Commit();
        }
    }
}

Containsは常にfalseであったため、すべてのオブジェクトで最初のLockが呼び出されました。問題は、NHibernateがデータベースIDとタイプによってオブジェクトを比較することです。 Containsは、equalsメソッドを使用します。このメソッドは、上書きされない場合、参照によって比較します。そのequalsメソッドを使用すると、例外なしで機能します。

public override bool Equals(object obj)
{
    if (this == obj) { 
        return true;
    } 
    if (GetType() != obj.GetType()) {
        return false;
    }
    if (Id != ((BaseObject)obj).Id)
    {
        return false;
    }
    return true;
}
7

Session.contains(Object obj)は参照をチェックし、同じ行を表し、既にその行にアタッチされている別のインスタンスを検出しません。

ここで、識別子プロパティを持つエンティティの私の一般的なソリューション。

public static void update(final Session session, final Object entity)
{
    // if the given instance is in session, nothing to do
    if (session.contains(entity))
        return;

    // check if there is already a different attached instance representing the same row
    final ClassMetadata classMetadata = session.getSessionFactory().getClassMetadata(entity.getClass());
    final Serializable identifier = classMetadata.getIdentifier(entity, (SessionImplementor) session);

    final Object sessionEntity = session.load(entity.getClass(), identifier);
    // override changes, last call to update wins
    if (sessionEntity != null)
        session.evict(sessionEntity);
    session.update(entity);
}

これは、私が好きな.Net EntityFrameworkの数少ない側面の1つであり、変更されたエンティティとそのプロパティに関するさまざまな接続オプションです。

4
djmj

セッションに既にアタッチされている可能性のある他のオブジェクトを説明する永続ストアからオブジェクトを「更新」するソリューションを思い付きました。

public void refreshDetached(T entity, Long id)
{
    // Check for any OTHER instances already attached to the session since
    // refresh will not work if there are any.
    T attached = (T) session.load(getPersistentClass(), id);
    if (attached != entity)
    {
        session.evict(attached);
        session.lock(entity, LockMode.NONE);
    }
    session.refresh(entity);
}
3
WhoopP

申し訳ありませんが、コメントを追加できないようです(まだですか?)。

Hibernate 3.5.0-Finalを使用する

Session#lockメソッドは廃止されましたが、javadoc doesSession#buildLockRequest(LockOptions)#lock(entity)を使用することを提案し、関連付けにcascade=lockがあることを確認すれば、遅延読み込みも問題になりません。

だから、私のアタッチ方法は少し似ています

MyEntity attach(MyEntity entity) {
    if(getSession().contains(entity)) return entity;
    getSession().buildLockRequest(LockOptions.NONE).lock(entity);
    return entity;

最初のテストでは、それが治療に役立つことが示唆されています。

2
Gwaptiva

たぶん、Eclipselinkではわずかに異なる動作をします。古くなったデータを取得せずに分離されたオブジェクトを再接続するには、通常次のようにします。

Object obj = em.find(obj.getClass(), id);

オプションの2番目のステップ(キャッシュを無効にする):

em.refresh(obj)
2
Hartmut P.

元の投稿には、update(obj)merge(obj)の2つのメソッドがありますが、これらは動作するように記載されていますが、逆の状況です。これが本当に当てはまる場合は、オブジェクトが既にセッション内にあるかどうかを最初にテストし、そうであればupdate(obj)を呼び出し、そうでなければmerge(obj)を呼び出してください。

セッションに存在するかどうかのテストはsession.contains(obj)です。したがって、次の擬似コードが機能すると思います。

if (session.contains(obj))
{
    session.update(obj);
}
else 
{
    session.merge(obj);
}
1

このオブジェクトを再アタッチするには、merge()を使用する必要があります。

このメソッドは、エンティティをデタッチし、エンティティを返すパラメータを受け入れ、データベースからアタッチしてリロードします。

Example :
    Lot objAttach = em.merge(oldObjDetached);
    objAttach.setEtat(...);
    em.persist(objAttach);
1
Ryuku

getHibernateTemplate()。replicate(entity、ReplicationMode.LATEST_VERSION)を試してください

1
Pavitar Singh

最初にmerge()(永続インスタンスを更新する)を呼び出し、次にlock(LockMode.NONE)(merge()によって返されたインスタンスではなく現在のインスタンスをアタッチする)がいくつかのユースケースで機能するようです。

0
cheesus