Hibernate3.6.7とJPA2.0について質問があります。
次のエンティティを検討してください(簡潔にするために、一部のゲッターとセッターは省略されています)。
@Entity
public class Parent {
@Id
@GeneratedValue
private int id;
@OneToMany(mappedBy="parent")
private List<Child> children = new LinkedList<Child>();
@Override
public boolean equals(Object obj) {
return id == ((Parent)obj).id;
}
@Override
public int hashCode() {
return id;
}
}
@Entity
public class Child {
@Id
@GeneratedValue
private int id;
@ManyToOne
private Parent parent;
public void setParent(Parent parent) {
this.parent = parent;
}
@Override
public boolean equals(Object obj) {
return id == ((Child)obj).id;
}
@Override
public int hashCode() {
return id;
}
}
ここで、このコードについて考えてみましょう。
// persist parent entity in a transaction
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Parent parent = new Parent();
em.persist(parent);
int id = parent.getId();
em.getTransaction().commit();
em.close();
// relate and persist child entity in a new transaction
em = emf.createEntityManager();
em.getTransaction().begin();
parent = em.find(Parent.class, id);
// *: parent.getChildren().size();
Child child = new Child();
child.setParent(parent);
parent.getChildren().add(child);
em.persist(child);
System.out.println(parent.getChildren()); // -> [Child@1, Child@1]
em.getTransaction().commit();
em.close();
子エンティティが、親エンティティの子のリストに誤って2回挿入されています。
次のいずれかを実行すると、コードは正常に機能します(リストに重複するエントリはありません)。
mappedBy
属性を削除します*
でマークされたコメント解除行)これは明らかに非常に奇妙な動作です。また、永続性プロバイダーとしてEclipseLinkを使用する場合、コードは期待どおりに機能します(重複はありません)。
これはHibernateのバグですか、それとも何かが足りませんか?
ありがとう
これはHibernateのバグです。驚いたことに、それはまだ報告されていません、 それを報告してください 。
初期化されていないレイジーコレクションに対する操作は、コレクションが初期化された後に実行するためにキューに入れられ、Hibernateは、これらの操作がデータベースのデータと競合する場合の状況を処理しません。通常、このキューはflush()
でクリアされ、競合する可能性のある変更がflush()
でもデータベースに伝播されるため、問題はありません。ただし、一部の変更(タイプIDENTITY
のジェネレーターによって生成されたIDを持つエンティティの永続化など)は、完全なflush()
なしでデータベースに伝播されます。ケースの競合が発生する可能性があります。
回避策として、子を永続化した後にセッションをflush()
することができます。
em.persist(child);
em.flush();
@OneToManyアノテーションが付けられたリストにアイテムを追加する際に問題が発生したときではなく、そのようなリストのアイテムを反復処理しようとしたときに、この質問に遭遇しました。リスト内の項目は常に重複しており、場合によっては2回以上重複していました。 (@ManyToManyアノテーションが付けられたときにも発生しました)。これらのリストでは要素の重複が許可されるはずだったため、ここではセットの使用は解決策ではありませんでした。
例:
_@OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
@Cascade(CascadeType.ALL)
@LazyCollection(LazyCollectionOption.FALSE)
private List entities;
_
結局のところ、Hibernateは_left outer join
_を使用してSQLステートメントを実行します。これにより、dbによって結果が重複して返される可能性があります。 OrderColumnを使用して、結果の順序を定義するだけで役立ちました。
@OrderColumn(name = "columnName")
コレクションに重複を追加しないようにHibernateに指示することで、この問題を修正しました。あなたの場合、children
フィールドのタイプを_List<Child>
_から_Set<Child>
_に変更し、Child
クラスにequals(Object obj)
とhashCode()
を実装します。
明らかに、これはすべての場合に可能であるとは限りませんが、Child
インスタンスが一意であることを識別するための適切な方法がある場合、このソリューションは比較的簡単です。
Java Wildfly(8.2.0-Final)のエンタープライズコンテキスト(Hibernateバージョン4.3.7だと思います)を使用することによる回避策は、最初に子を永続化して、それに追加することでした。怠惰なコレクション:
...
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void test(){
Child child = new Child();
child.setParent(parent);
childFacade.create(child);
parent.getChildren().add(cild);
parentFacade.edit(parent);
}