13

次のように定義された2つのエンティティBeanがあります(無関係なものは削除されています):

@Entity
@Table(...)
public class MasterItem implements java.io.Serializable {

  private Set<CriticalItems> criticalItemses = new HashSet<CriticalItems>(0);

  @OneToMany(fetch = FetchType.EAGER, mappedBy = "masterItem", orphanRemoval = true,
            cascade = {javax.persistence.CascadeType.DETACH})
    @Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE})
    public Set<CriticalItems> getCriticalItemses() {
        return this.criticalItemses;
    }
}

CriticalItems は次のように定義されます。

@Entity
@Table(...)
public class CriticalItems implements java.io.Serializable {

    private MasterItem masterItem;

    @ManyToOne(fetch = FetchType.LAZY, optional = false,
            cascade = {javax.persistence.CascadeType.DETACH})
    @Cascade({CascadeType.SAVE_UPDATE})
    @JoinColumn(name = "mi_item_id", nullable = false)
    public MasterItem getMasterItem() {
        return this.masterItem;
    }
}

私のDAOコードには、次のメソッドがあります。

public MasterItem load(int id) {
    MasterItem results = (MasterItem) getSessionFactory().getCurrentSession()
        .get("com.xxx.MasterItem", id);

}

public void save(MasterItem master) {
    // master has been changed by the UI since it
    getSessionFactory().getCurrentSession().saveOrUpdate(master);
}

MasterItem をロードすると、正しくロードされ、CriticalItems Set にも指示どおりにデータがロードされます。次に、このデータを UI に送信し、更新されたコピーを取得して保持します。ユーザーは MasterItem オブジェクトのフィールドを更新しますが、CriticalItems セットやその中の何かには触れず、変更されないままです。

私の save() メソッドが呼び出されると、Hibernate は Set of CriticalItems 内の各アイテムの SQL 更新を送信することを主張しますが、それらのどれもまったく変更されていません。

掘り下げた後、これが私が起こっていると思うことです。saveOrUpdate() を実行すると、Hibernate は MasterItem オブジェクトが切り離された状態にあることを認識し、ディスクからリロードしようとします。ただし、そうすると、準備済みステートメント (起動時に Hibernate によって自動作成されたもの) を使用しているように見え、この準備済みステートメントは CriticalItems データへの結合を試みません。

したがって、Hibernate には CriticalItems の完全な Set を持つ更新された MasterItem オブジェクトがありますが、コレクションのない MasterItem を「previousState」オブジェクトとして使用します。したがって、すべての CriticalItems は SQL 経由で更新されます (挿入されません。これ自体が興味深いことです)。

この動作を引き起こす注釈で何かをしましたか? Interceptor を使用してアイテムが実際に変更されたことを確認したり、ダーティ フラグを変更して Hibernate のデフォルト アルゴリズムをオーバーライドしたりできることはわかっていますが、これは Hibernate が私の介入なしに独自に処理する必要があるようです。

任意の洞察をいただければ幸いです。

更新: コメントに基づいて、saveOrUpdate() と merge() の違いを理解していると思います。saveOrUpdate() は、すべての場合に SQL INSERT または SQL UPDATE のいずれかになり、理論的には、オブジェクトが永続的な状態から変更された場合にのみマージが更新を発行することを認識していますが、それを判断するために、Hibernate最初に SQL SELECT を介してオブジェクトをリロードする必要があります。

そのため、コードに戻って saveOrUpdate() を merge() に変更すればうまくいくと思っていましたが、そうではありませんでした。

マージ()を使用したとき、私は得ていました

org.springframework.orm.hibernate3.HibernateSystemException: could not initialize proxy - no Session; nested exception is org.hibernate.LazyInitializationException: could not initialize proxy - no Session

しかし、saveOrUpdate() に戻すとうまくいきました。

ついに理由がわかりました-注釈に含めませんでした(うーん)CascadeType.MERGE@Cascadeそれを修正すると、例外はなくなりました。

4

2 に答える 2

19

That's the semantical difference between update() and merge().

From Christian Bauer and Gavin King's Java Persistence with Hibernate (I can't find clear explanation of this behaviour in the Hibernate docs):

The update() method forces an update to the persistent state of the object in the database, always scheduling an SQL UPDATE.
...
It doesn’t matter if the item object is modified before or after it’s passed to update().
...
Hibernate always treats the object as dirty and schedules an SQL UPDATE., which will be executed during flush.

On the other hand, merge() queries the database first, and doesn't perform update if state haven't changed.

So, if you want Hibernate to query the database first, you need to use merge() (though default behaviour of update() can be overriden by specifing @org.hibernate.annotations.Entity(selectBeforeUpdate = true) on your entities).

于 2011-01-18T16:23:41.897 に答える
1

エンティティにリビジョン列 (楽観的ロック) を追加してみてください

@Version
Date lastModified;
于 2011-01-18T15:36:16.297 に答える