2011-10-26 38 views
18

Tôi có một câu hỏi liên quan đến Hibernate 3.6.7 và JPA 2.0.Hibernate chèn các bản sao vào bộ sưu tập @OneToMany

Xem xét đối tượng sau đây (một số getter và setter bị bỏ qua cho ngắn gọn):

@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; 
    } 
} 

Bây giờ xem xét đoạn mã này:

// 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(); 

Thực thể con được sai được chèn hai lần vào danh sách của trẻ em của thực thể mẹ.

Khi thực hiện một trong những điều sau đây, mã hoạt động tốt (không có mục trùng lặp trong danh sách):

  • loại bỏ các thuộc tính mappedBy trong thực thể cha mẹ
  • thực hiện một số thao tác đọc thuộc danh mục con (ví dụ: dòng ghi chú được đánh dấu bởi *)

Đây rõ ràng là một hành vi rất lạ. Ngoài ra, khi sử dụng EclipseLink làm nhà cung cấp kiên trì, mã hoạt động như mong đợi (không có bản sao).

Đây có phải là lỗi Hibernate hoặc tôi thiếu gì đó không?

Cảm ơn

+0

Bạn có thể thêm mã của phương pháp setParent và equals/phương pháp hashCode? –

+0

Tôi vừa thêm các phương thức bạn đã yêu cầu. Tuy nhiên, tôi không nghĩ rằng vấn đề này có liên quan đến equals/hashCode. – user1014562

+1

Phương thức equals của bạn không tôn trọng hợp đồng của Object.equals. Hơn nữa, hashCode thay đổi khi ID được tạo và gán cho thực thể. Tôi sẽ không ngạc nhiên nếu lỗi biến mất khi bạn xóa hashCode và bằng. BTW. Hibernate khuyến cáo không sử dụng ID để thực thi equals và hashCode. –

Trả lời

27

Đó là lỗi trong Hibernate. Đáng ngạc nhiên, nó chưa được báo cáo, feel free to report it.

Các hoạt động chống lại các tập hợp lười không khởi tạo được xếp hàng đợi để thực thi chúng sau khi bộ sưu tập được khởi tạo và Hibernate không xử lý tình huống khi các hoạt động này xung đột với dữ liệu từ cơ sở dữ liệu. Thông thường nó không phải là một vấn đề, bởi vì hàng đợi này được xóa khi flush(), và các thay đổi xung đột có thể được truyền cho cơ sở dữ liệu khi flush() là tốt. Tuy nhiên, một số thay đổi (chẳng hạn như sự kiên trì của các thực thể có id được tạo bởi máy phát kiểu IDENTITY, tôi đoán, đó là trường hợp của bạn) được truyền đến cơ sở dữ liệu mà không có đầy đủ flush() và trong các trường hợp này xung đột là có thể.

Là một workaround bạn có thể flush() phiên sau khi kiên trì con:

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

Cảm ơn câu trả lời của bạn và giải pháp thay thế! Tôi đã tạo ra một báo cáo lỗi và được gọi là câu trả lời của bạn: [HHH-6776] (https://hibernate.onjira.com/browse/HHH-6776) – user1014562

+0

Thật là lạ khi lỗi này chưa được giải quyết? Thật nghèo nàn khi cần phải đọc các vật thể trước khi bền bỉ. – nize

+0

Tôi cũng có bản sao trong oneToMany với một danh sách. Tuy nhiên, việc thay đổi thành thiết lập có giúp ích gì không, tại sao tôi buộc phải sử dụng thiết lập khi tôi muốn sử dụng danh sách? Sử dụng truy vấn sql được tạo ra của trả về ngủ đông KHÔNG có bản sao. Tuy nhiên, hibernate trả về các bản sao trong danh sách ngay cả khi truy vấn không có các bản sao được trả về. Không thể tìm thấy whithout giải pháp bằng cách sử dụng bộ. Tôi không cần phải sử dụng tuôn ra, vì đậu không quốc tịch .. – nimo23

1

Bằng cách sử dụng Java bối cảnh doanh nghiệp trong Wildfly (8.2.0 Final) (Tôi nghĩ đó là Hibernate phiên bản 4.3.7) thực hiện giải pháp đối với tôi, trước hết hãy kiên trì đứa trẻ thêm nó vào bộ sưu tập lười biếng:

... 
@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

Tôi đã khắc phục vấn đề này bằng cách yêu cầu Hibernate không thêm các bản sao trong bộ sưu tập của tôi. Trong trường hợp của bạn, hãy thay đổi loại trường children của bạn từ List<Child> thành Set<Child> và triển khai equals(Object obj)hashCode() trên lớp Child. Rõ ràng điều này sẽ không thể xảy ra trong mọi trường hợp, nhưng nếu có một cách lành mạnh để xác định rằng một cá thể Child là duy nhất thì giải pháp này có thể tương đối không gây đau đớn.

+0

Và thiết lập @OneToMany ((. ..), cascade = CascadeType.MERGE) – Hinotori

2

Tôi đã xem qua câu hỏi này khi tôi gặp vấn đề với việc thêm các mục vào danh sách được chú thích bằng @OneToMany, nhưng khi cố gắng lặp lại các mục của danh sách như vậy. Các mục trong danh sách luôn được nhân đôi, đôi khi nhiều hơn hai lần. (Cũng xảy ra khi được chú thích với @ManyToMany). Sử dụng một Set không phải là một giải pháp ở đây, vì các danh sách này được cho là cho phép các phần tử trùng lặp trong chúng.

Ví dụ:

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

Khi nó bật ra, Hibernate thực thi câu lệnh SQL sử dụng left outer join, mà có thể dẫn đến kết quả trùng lặp được trả về bởi các db. Điều gì đã giúp đơn giản là xác định thứ tự trên kết quả, sử dụng OrderColumn:

@OrderColumn(name = "columnName")
+0

không thể cảm ơn đủ, bạn có thể chỉ cho tôi một tài nguyên mà tôi có thể đọc thêm về điều này không? –

0

Quản lý điều này bằng cách chỉ cần gọi phương thức empty(). Đối với kịch bản này,

parent.getChildren().isEmpty()

trước

parent.getChildren().add(child); 
Các vấn đề liên quan