204

同じ ID のオブジェクトがセッションに既に存在する場合があり、エラーが発生する可能性がありますが、切り離されたオブジェクトを休止状態のセッションに再接続する必要がある状況があります。

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

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

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

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

4

18 に答える 18

189

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

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

refresh()デタッチされたエンティティで呼び出すことはできません。

lock()デタッチされたエンティティで呼び出すことはできません。呼び出し可能であり、エンティティを再アタッチしました。引数「LockMode.NONE」を指定して「lock」を呼び出すと、ロックされているがロックされていないことを意味します。これは、API設計の最も直感に反する部分です。私が今まで見てきました。

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

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

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

ミック

于 2010-12-14T10:50:32.087 に答える
36

これらの答えはすべて、重要な違いを見逃しています。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() を使用してください...

于 2012-05-17T21:26:01.277 に答える
19

外交的でない答え:おそらく、拡張された持続性コンテキストを探しているのでしょう。これがSeam Frameworkの背後にある主な理由の 1 つです ... 特に Spring で Hibernate を使用するのに苦労している場合は、Seam のドキュメントのこの部分をチェックしてください。

外交的な答え:これはHibernate docsで説明されています。さらに明確にする必要がある場合は、Java Persistence with Hibernateのセクション 9.3.2 の「Working with Detached Objects」を参照してください。Hibernate で CRUD 以外のことをしている場合は、この本を入手することを強くお勧めします。

于 2009-05-29T00:41:17.397 に答える
16

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

session.lock(entity, LockMode.NONE);

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

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

継承をマップする場合を除いて、get を使用することもできます (getId() で既に例外がスローされます)。

entity = session.get(entity.getClass(), entity.getId());
于 2010-09-10T09:04:06.607 に答える
10

JavaDoc for に戻ってorg.hibernate.Session、次のことを見つけました。

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

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

update(): 同じ識別子を持つ永続的なインスタンスが存在する場合、例外がスローされます。

saveOrUpdate(): 保存または更新

lock(): 非推奨

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

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

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

于 2012-12-15T14:43:13.550 に答える
7

私は 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 であるため、すべてのオブジェクトで First 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;
}
于 2012-08-21T07:02:08.177 に答える
4

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 つであり、変更されたエンティティとそのプロパティに関するさまざまなアタッチ オプションです。

于 2014-07-15T00:06:58.197 に答える
3

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

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);
}
于 2012-05-17T20:52:26.720 に答える
2

おそらく、Eclipselink では動作が少し異なります。古いデータを取得せずに切り離されたオブジェクトを再接続するには、通常、次のようにします。

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

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

em.refresh(obj)
于 2015-10-25T18:15:48.997 に答える
1

元の投稿では、2 つの方法がupdate(obj)あり、merge(obj)それが機能すると述べられていますが、反対の状況です。これが本当なら、最初にオブジェクトがすでにセッションにあるかどうかをテストしupdate(obj)、そうでない場合は を呼び出し、そうでない場合は を呼び出してみませんかmerge(obj)

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

if (session.contains(obj))
{
    session.update(obj);
}
else 
{
    session.merge(obj);
}
于 2011-02-24T18:17:19.440 に答える
1

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

于 2009-05-28T05:58:48.397 に答える
0

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

于 2011-01-05T16:45:41.887 に答える
0

プロパティhibernate.allow_refresh_detached_entityは私のためにトリックをしました。ただし、これは一般的なルールなので、場合によってのみ実行したい場合にはあまり適していません。お役に立てば幸いです。

Hibernate 5.4.9 でテスト済み

SessionFactoryOptionsBuilder

于 2019-12-11T11:44:07.680 に答える