2012-05-11 10 views
23

devo queste due classiIllegalStateException con Hibernate 4 e ManyToOne cascading

MyItem Oggetto:

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

E sto Tring a persistere quelli con il seguente codice:

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

Anche se questo funziona bene con Hibernate 3.6, Hibernate 4.1.3 tiri

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) 

back-end del database è h2 (ma lo stesso accade con hsqldb o derby). Che cosa sto facendo di sbagliato?

risposta

0

Provare ad aggiungere l'annotazione nella sezione Componente alla voce @Id. altrimenti due istanze diverse potrebbero ottenere lo stesso id e collidere.

Sembra che tu stia dando loro lo stesso ID.

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

Questo potrebbe risolvere il problema.

+0

Sfortunatamente, entrambi gli ID non sono generati dal database, ma impostati esplicitamente. –

+0

Oops, intendevo la classe Component. stai dando loro lo stesso id? –

+0

Sì, questa è l'idea alla base. Entrambi i componenti ottengono lo stesso id e sono uguali (vedere l'istruzione equals sopra). Quindi la cascata dovrebbe occuparsi di questo, giusto? Si potrebbe anche provare a utilizzare lo stesso riferimento (ad esempio c1) per entrambe le variabili (defaultComponent e masterComponent), poiché sono uguali in ogni caso. –

5

Lo stesso qui, controlla il tuo metodo equals(). Molto probabilmente è mal implementato.

Modifica: Ho verificato che un'operazione di unione non funzionerà se non si implementano correttamente i metodi equals() e hashCode() di Entity.

Si dovrebbe seguire queste linee guida per equals attuazione() e hashCode():.

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

"Si consiglia di implementare equals() e hashCode() utilizzando Affari uguaglianza Tasto Affari uguaglianza significa che il metodo equals() confronta solo le proprietà che formano la chiave aziendale. È una chiave che identificherebbe la nostra istanza nel mondo reale (una chiave candidata naturale) "

Ciò significa: NON si deve usare il tuo ID come parte del tuo equ implementazione als()!

+1

Ulteriori informazioni: http://blog.andrewbeacock.com/2008/08/how-to-implement-hibernate-safe-equals.html –

0

Se il nome è l'ID, perché stai creando due oggetti con lo stesso id ?? puoi usare l'oggetto c1 in tutto il codice.

Se questo è solo un esempio e si crea l'oggetto c2 in un'altra parte del codice, allora non si dovrebbe creare un nuovo oggetto, ma caricarlo dal database:

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

è il suo rapporto tra la voce e componente unidirezionale o bidirezionale? Se è bidirezionale, assicurati di non avere chiamate Cascade.MERGE risalenti all'elemento.

Fondamentalmente, la versione più recente di Hibernate ha una mappa delle entità che contiene un elenco di tutte le cose che devono essere unite in base alla chiamata per unire() chiamerà fusione e quindi passerà alla successiva, ma manterrà le cose nella mappa, genererà l'errore che hai indicato sopra "Una copia di entità è stata già assegnata a un'altra entità" quando incontra un oggetto che è già stato trattato. Abbiamo trovato nella nostra app quando abbiamo individuato queste fusioni "al rialzo" nel grafico dell'oggetto, ad esempio. su collegamenti bidirezionali, ha risolto la chiamata di unione.

0

In base alla logica in EventCache, tutte le entità nel grafico oggetto devono essere univoche. Quindi la soluzione migliore (o funziona?) È rimuovere la cascata in MyItem su Component. E unisci separatamente Componente se è davvero necessario. Scommetto che nel 95% dei casi Componente non dovrebbe essere unito secondo la logica aziendale.

D'altra parte - mi interessa davvero conoscere i veri pensieri dietro quella restrizione.

27

Ho avuto lo stesso problema, e questo è quello che ho trovato:

Il metodo di fusione attraversa il grafico dell'oggetto che si desidera memorizzare, e per ogni oggetto in questo grafico viene caricato dal database, quindi ha una coppia di (entità persistente, entità distaccata) per ogni oggetto nel grafico, dove entità distaccata è l'entità che verrà immagazzinata, e l'entità persistente è ottenuta dal database. (Nel metodo, così come nel messaggio di errore, l'entità persistente è nota come 'copia'). Quindi queste coppie sono messe in due mappe, una con l'entità persistente come chiave e l'entità distaccata come valore, e una con l'entità distaccata come chiave e l'entità persistente come valore.

Per ciascuna coppia di tali entit, controlla queste mappe, per vedere se l'entità persistente esegue il mapping sulla stessa entità distaccata di prima (se è già stata visitata) e viceversa. Questo problema si verifica quando ottieni una coppia di entità in cui ottenere un get con l'entità persistente restituisce un valore, ma un get dall'altra mappa, con l'entità distaccata restituisce null, il che significa che hai già collegato l'entità persistente con una distaccata entità con un hashcode diverso (in pratica l'identificativo dell'oggetto se non si è sovrascritto il metodo hashcode).

TL; DR, si dispone di più oggetti con identificatori di oggetto/codice di errore diversi, ma con lo stesso identificatore di persistenza (facendo quindi riferimento alla stessa entità persistente). Questo non è più consentito nelle nuove versioni di Hibernate4 (4.1.3.Finale e ascendente da quello che potrei dire).

Il messaggio di errore non è molto buona imo, quello che realmente dovrebbe dire è qualcosa di simile:

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

o

Multiple detached objects corresponding to the same persistent entity

+1

Il problema esiste ancora con la versione "4.1.10 Finale". Tornando alla versione "4.1.2 Finale", però, funziona. – Zaki

+1

Ok, non riesco davvero a ricordare quali versioni ho provato oltre 4.1.10.Final e 4.1.1.Final (che è quello che ho finito per tornare a), ma se 4.1.2.Final funziona aggiornerò il mio rispondi per riflettere questo – Tobb

+1

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

2

avuto la stessa eccezione (hibernate 4.3.0 .CR2) stancante per salvare un oggetto che sta avendo due copie di un oggetto figlio, è stato risolto da, nell'entità da:

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

a poco,

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

non so il motivo se

0

Ho avuto lo stesso problema, ho appena risolto. Mentre le risposte di cui sopra possono risolvere il problema, non sono d'accordo con alcune di esse, specialmente modificando i metodi equlas() e hashcode() implementati. Tuttavia ritengo che la mia risposta rafforzi la risposta di @Tobb e @Supun s.

Da parte mia Molti (lato bambino) ho avuto

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

E sul mio un lato (lato genitore)

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

Dopo aver letto la risposta superiore eccellente fornito da @Tobb e un po ' un po 'di pensiero mi sono reso conto che le annotazioni non avevano senso. Il modo in cui ho capito (nel mio caso) stavo unendo() l'oggetto Autore e unendo() l'oggetto del libro. Ma poiché la raccolta di libri è un componente dell'oggetto Autore, stava tentando di salvarla due volte. La mia soluzione era quella di cambiare i tipi di cascata a:

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

e

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

per fare una lunga storia breve, Persistere l'oggetto padre e unire l'oggetto figlio.

Spero che questo aiuti/abbia senso.

Problemi correlati