2012-05-11 20 views
23

Tôi đã có những hai lớpIllegalStateException với Hibernate 4 và ManyToOne Cascading

myItem Object:

@Entity 
public class MyItem implements Serializable { 

    @Id 
    private Integer id; 
    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) 
    private Component defaultComponent; 
    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) 
    private Component masterComponent; 

    //default constructor, getter, setter, equals and hashCode 
} 

Component Object:

@Entity 
public class Component implements Serializable { 

    @Id 
    private String name; 

    //again, default constructor, getter, setter, equals and hashCode 
} 

Và tôi Tring để tồn tại những người có đoạn mã sau:

public class Test { 

    public static void main(String[] args) { 
     Component c1 = new Component(); 
     c1.setName("comp"); 
     Component c2 = new Component(); 
     c2.setName("comp"); 
     System.out.println(c1.equals(c2)); //TRUE 

     MyItem item = new MyItem(); 
     item.setId(5); 
     item.setDefaultComponent(c1); 
     item.setMasterComponent(c2); 

     ItemDAO itemDAO = new ItemDAO(); 
     itemDAO.merge(item); 
    } 
} 

Trong khi điều này hoạt động tốt với Hibernate 3.6, Hibernate 4.1.3 ném

Exception in thread "main" java.lang.IllegalStateException: An entity copy was already assigned to a different entity. 
     at org.hibernate.event.internal.EventCache.put(EventCache.java:184) 
     at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:285) 
     at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151) 
     at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:914) 
     at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:896) 
     at org.hibernate.engine.spi.CascadingAction$6.cascade(CascadingAction.java:288) 
     at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:380) 
     at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:323) 
     at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:208) 
     at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:165) 
     at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:423) 
     at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:213) 
     at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:282) 
     at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151) 
     at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:76) 
     at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:904) 
     at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:888) 
     at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:892) 
     at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:874) 
     at sandbox.h4bug.Test$GenericDAO.merge(Test.java:79) 
     at sandbox.h4bug.Test.main(Test.java:25) 

Cơ sở dữ liệu backend là h2 (nhưng tương tự xảy ra với HSQLDB hoặc derby). Tôi đang làm gì sai?

Trả lời

0

Thử thêm chú thích @GeneratedValue dưới @Id trong lớp Thành phần. nếu không hai trường hợp khác nhau có thể nhận cùng một id và va chạm.

Đường nối mà bạn đang cung cấp cho chúng cùng một ID.

Component c1 = new Component(); 
    c1.setName("comp"); 
    Component c2 = new Component(); 
    c2.setName("comp"); 

Điều đó chỉ có thể giải quyết vấn đề của bạn.

+0

Thật không may, cả hai Id không phải là cơ sở dữ liệu được tạo nhưng đặt rõ ràng. –

+0

Rất tiếc, tôi có nghĩa là lớp Thành phần. bạn đang cho họ cùng một id? –

+0

Vâng, đó là ý tưởng đằng sau nó. Cả hai thành phần có cùng id và bằng nhau (xem câu lệnh equals ở trên). Vì vậy, thác nên chăm sóc này, phải không? Thậm chí bạn có thể thử sử dụng cùng một tham chiếu (ví dụ c1) cho cả biến (defaultComponent và masterComponent), vì chúng đều bằng nhau. –

5

Tương tự tại đây, hãy kiểm tra phương thức equals() của bạn. Hầu hết có thể được triển khai kém.

Chỉnh sửa: Tôi đã xác minh rằng thao tác hợp nhất sẽ không hoạt động nếu bạn không triển khai đúng phương thức equals() và hashCode() của mình.

Bạn nên làm theo các hướng dẫn để thực hiện equals() và hashCode():.

http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html/ch04.html#persistent-classes-equalshashcode

"Chúng tôi đề nghị bạn thực hiện equals() và hashCode() sử dụng kinh doanh quan trọng bình đẳng chính kinh doanh bình đẳng có nghĩa là phương trình equals() chỉ so sánh các thuộc tính tạo thành khóa kinh doanh. Đây là khóa xác định cá thể của chúng ta trong thế giới thực (một khóa ứng cử viên tự nhiên) "

Điều đó có nghĩa là: Bạn KHÔNG nên sử dụng Id của bạn như là một phần của equ als() thực hiện!

+1

Thông tin thêm: http://blog.andrewbeacock.com/2008/08/how-to-implement-hibernate-safe-equals.html –

0

Nếu tên là Id tại sao bạn tạo hai đối tượng có cùng id? bạn có thể sử dụng đối tượng c1 trong tất cả các mã.

Nếu đó là chỉ là ví dụ và bạn tạo đối tượng c2 trong một phần khác của mã, sau đó bạn không nên tạo ra một đối tượng mới nhưng tải nó từ cơ sở dữ liệu:

c2 = itemDao.find("comp", Component.class); //or something like this AFTER the c1 has been persisted 
4

là mối quan hệ của bạn giữa các mục và thành phần unidirectional hoặc hai chiều? Nếu đó là hai chiều đảm bảo bạn không có các cuộc gọi Cascade.MERGE quay lại Mục.Về cơ bản, phiên bản mới hơn của Hibernate có một bản đồ thực thể chứa danh sách tất cả những thứ cần được hợp nhất dựa trên cuộc gọi để hợp nhất() nó sẽ gọi hợp nhất và sau đó chuyển sang kế tiếp, nhưng giữ mọi thứ trong bản đồ, nó sẽ ném lỗi bạn nêu ở trên "Bản sao thực thể đã được gán cho một thực thể khác" khi nó gặp một mục đã được xử lý. Chúng tôi tìm thấy trong ứng dụng của mình khi chúng tôi định vị những sự kết hợp "trở lên" này trong đồ thị đối tượng tức là. trên các liên kết hai chiều, nó đã sửa cuộc gọi hợp nhất.

0

Theo logic trong EventCache, tất cả các thực thể trong biểu đồ đối tượng phải là duy nhất. Vì vậy, giải pháp tốt nhất (hoặc là nó làm việc xung quanh?) Là để loại bỏ cascade trong MyItem thành phần. Và hợp nhất thành phần riêng biệt nếu nó thực sự cần thiết - tôi sẽ đặt cược rằng trong 95% các trường hợp Thành phần không được hợp nhất theo logic nghiệp vụ.

Mặt khác - Tôi thực sự quan tâm để biết những suy nghĩ thực sự đằng sau hạn chế đó.

27

tôi đã cùng một vấn đề, và điều này là những gì tôi tìm thấy:

Phương pháp hợp nhất đi qua đồ thị của đối tượng mà bạn muốn để lưu trữ, và cho từng đối tượng trong đồ thị này nó tải nó từ cơ sở dữ liệu, do đó, nó có một cặp (thực thể liên tục, thực thể tách rời) cho từng đối tượng trong biểu đồ, trong đó thực thể tách rời là thực thể sẽ được lưu trữ và thực thể liên tục được lấy từ cơ sở dữ liệu. (Trong phương thức, cũng như trong thông báo lỗi, thực thể liên tục được gọi là 'copy'). Sau đó, các cặp này được đặt trong hai bản đồ, một cặp với thực thể liên tục là khóa và thực thể tách rời làm giá trị và một với thực thể tách rời là khóa và thực thể liên tục làm giá trị.

Đối với mỗi cặp như vậy, nó sẽ kiểm tra các bản đồ này, để xem thực thể liên tục có ánh xạ tới cùng một thực thể tách rời như trước đó (nếu nó đã được truy cập) và ngược lại. Vấn đề này xảy ra khi bạn nhận được một cặp thực thể khi thực hiện một đối tượng liên tục trả về một giá trị, nhưng nhận được từ bản đồ khác, với thực thể tách rời trả về null, có nghĩa là bạn đã liên kết thực thể liên tục với một tách rời thực thể với một hashcode khác (về cơ bản là mã định danh đối tượng nếu bạn không ghi đè phương thức hashcode).

TL; DR, bạn có nhiều đối tượng với số nhận dạng đối tượng/mã băm khác nhau, nhưng có cùng định danh liên tục (do đó tham chiếu cùng một thực thể liên tục). Điều này là appearantly không còn được cho phép trong các phiên bản mới hơn của Hibernate4 (4.1.3.Final và trở lên từ những gì tôi có thể nói).

Các thông báo lỗi không phải là rất tốt imo, những gì nó thực sự cần nói là cái gì đó như:

A persistent entity has already been assigned to a different detached entity

hoặc

Multiple detached objects corresponding to the same persistent entity

+1

Sự cố vẫn tồn tại với phiên bản "4.1.10 Final". Hoàn nguyên về phiên bản "4.1.2 Final" hoạt động. – Zaki

+1

Ok, tôi thực sự không thể nhớ phiên bản nào tôi đã thử bên cạnh 4.1.10.Final và 4.1.1.Final (đây là phiên bản mà tôi đã hoàn nguyên), nhưng nếu 4.1.2.Các tác phẩm tôi sẽ cập nhật trả lời để phản ánh điều này. – Tobb

+1

Xem https://hibernate.atlassian.net/browse/HHH-7605 – Zaki

2

Đã cùng một ngoại lệ (hibernate 4.3.0 .CR2) mệt mỏi để lưu một đối tượng có hai bản sao của một đối tượng con, được cố định bởi, trong thực thể từ:

@OneToOne(cascade = CascadeType.MERGE) 
private User reporter; 
@OneToOne(cascade = CascadeType.MERGE) 
private User assignedto; 

chỉ,

@OneToOne 
private User reporter; 
@OneToOne 
private User assignedto; 

tôi không biết lý do mặc dù

0

Tôi đã gặp phải sự cố tương tự, chỉ cần giải quyết vấn đề. Trong khi các câu trả lời ở trên có thể giải quyết vấn đề tôi không đồng ý với một vài trong số họ, đặc biệt là thay đổi phương thức equlas() và hashcode() đã triển khai. Tuy nhiên tôi cảm thấy như câu trả lời của tôi củng cố @Tobb và @Supun s 'câu trả lời (s).

On Nhiều phụ (side trẻ em) của tôi, tôi đã

@OneToMany(mappedBy = "authorID", cascade =CascadeType.ALL, fetch=FetchType.EAGER) 
private Colllection books; 

Và ở một bên của tôi (bên cha mẹ)

@ManyToOne(cascade =CascadeType.ALL) 
private AuthorID authorID; 

Sau khi đọc câu trả lời đầu tuyệt vời cung cấp bởi @Tobb và một chút một chút suy nghĩ tôi nhận ra rằng các chú thích không có ý nghĩa. Cách tôi hiểu nó (trong trường hợp của tôi) Tôi đã sáp nhập() đối tượng Tác giả và sáp nhập() đối tượng cuốn sách. Nhưng vì bộ sưu tập sách là một thành phần của đối tượng Tác giả, nó đang cố gắng lưu nó hai lần. Giải pháp của tôi đã thay đổi các kiểu thác để:

@OneToMany(mappedBy = "authorID", cascade =CascadeType.PERSIST, fetch=FetchType.EAGER) 
    private Collection bookCollection; 

@ManyToOne(cascade =CascadeType.MERGE) 
private AuthorID authorID; 

Để thực hiện một câu chuyện dài ngắn, Persist đối tượng phụ huynh và hợp nhất các đối tượng trẻ em.

Hy vọng điều này sẽ giúp/có ý nghĩa.