2011-10-26 150 views
18

我有一個關於Hibernate 3.6.7和JPA 2.0的問題。Hibernate插入重複項到@OneToMany集合

考慮下面的實體(一些getter和setter是爲了簡潔省略):

@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()); // -> [[email protected], [email protected]] 

em.getTransaction().commit(); 
em.close(); 

子實體被錯誤地插入兩次到列表的父實體的子女。

當執行下列操作之一,代碼工作正常(無重複的條目列表):

  • 刪除mappedBy屬性在父實體
  • 執行兒童名單上的一些讀操作(例如,標註爲*的取消註釋行)

這顯然是一個非常奇怪的行爲。另外,使用EclipseLink作爲持久性提供者時,代碼的工作方式與預期的一樣(不重複)。

這是一個Hibernate的bug還是我錯過了什麼?

謝謝

+0

之前,您可以加入該方法的setParent和的平等/ hashCode方法的代碼? –

+0

我剛剛添加了您要求的方法。但是,我不認爲這個問題與equals/hashCode相關。 – user1014562

+1

您的equals方法不尊重Object.equals的合約。此外,hashCode在ID生成並分配給實體時發生變化。如果在刪除hashCode和equals時該錯誤消失,我不會感到驚訝。 BTW。 Hibernate建議不要使用ID來實現equals和hashCode。 –

回答

27

這是Hibernate的一個bug。令人驚訝的是,目前還沒有報道,feel free to report it

針對未初始化的懶惰集合的操作會進行排隊,以便在集合初始化後執行它們,並且當這些操作與數據庫中的數據發生衝突時,Hibernate不處理這種情況。通常這不是問題,因爲此隊列在flush()上被清除,並且可能衝突的更改也會在flush()上傳播到數據庫。然而,有些更改(例如,由IDENTITY類型的生成器生成的ID持久化的實體,我猜是您的情況)會傳播到數據庫而沒有完整的flush(),並且在這些情況下可能會發生衝突。

作爲一種變通方法,你可以堅持的孩子後flush()會話:

em.persist(child); 
em.flush(); 
+1

感謝您的回答和解決方法!我創建了一個錯誤報告並提到你的答案:[HHH-6776](https://hibernate.onjira.com/browse/HHH-6776) – user1014562

+0

這個錯誤還沒有解決,這真的很奇怪嗎?在堅持之前需要閱讀這些物體是非常困難的。 – nize

+0

我也有重複oneToMany與列表。但是,更改設置確實有幫助,爲什麼當我想要使用列表時,我被迫使用set?使用生成的hibernate的sql查詢返回沒有重複。但是,即使查詢沒有返回重複項,hibernate也會在列表中返回重複項。沒有使用set的情況下找不到解決方案。我不需要使用flush,因爲無狀態bean。 – nimo23

1

通過使用Wildfly(8.2.0決賽)的Java企業環境中(我認爲這是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); 
} 
2

我告訴Hibernate的不是我的收藏中添加重複固定的這個問題。在你的情況下,將children字段的類型從List<Child>更改爲Set<Child>,並在Child類中執行equals(Object obj)hashCode()

顯然這在每一種情況下都是不可能的,但是如果有一種明智的方法來確定一個Child實例是獨特的,那麼這個解決方案可以是相對無痛的。

+0

並設置@OneToMany((。 ..),級聯= CascadeType.MERGE) – Hinotori

2

我遇到了這個問題,當我遇到問題而不是將項目添加到用@OneToMany註釋的列表中時,但是嘗試遍歷這樣的列表項時。列表中的項目總是重複的,有時甚至超過兩次。 (在使用@ManyToMany註釋時也會發生)。在這裏使用Set不是一個解決方案,因爲這些列表應該允許它們中有重複的元素。

實施例:

@OneToMany(mappedBy = "parent", fetch = FetchType.EAGER) 
@Cascade(CascadeType.ALL) 
@LazyCollection(LazyCollectionOption.FALSE) 
private List entities; 

事實證明,休眠使用left outer join,這可導致在由db返回重複結果來執行SQL語句。使用OrderColumn簡單地定義結果的順序有什麼幫助:

@OrderColumn(name = "columnName")
+0

不能夠感謝你,你能指點我一個資源,我可以閱讀更多關於這個? –

0

只需調用empty()方法即可管理此操作。對於這種情況,

parent.getChildren().isEmpty()

parent.getChildren().add(child);