2009-07-09 15 views
14

Vorrei fare una copia profonda di un'entità in JPA. Ho trovato una discussione interessante qui: http://forums.java.net/jive/thread.jspa?messageID=253092&tstart=0Deep Copy in JPA

Sembrava che la soluzione proposta fosse quella di impostare tutti gli @ Id a zero. Ecco il mio codice di base:


//Start a JPA session. 
EntityManager em= emf.createEntityManager(); 
em.getTransaction().begin(); 

//Get the object I want to copy. 
MyClass myObject=em.find(MyClass.class,id); 

//Use reflection to find @Id's and set them to zero for all @OneToMany and @OneToOne relations. 
//TODO: write the ugly recursive code to do this. 

//Hoping this will create a deep copy. 
em.merge(myObject); 

//Close the session. 
em.getTransaction().commit(); 
em.close(); 

È una buona strategia? Qualcuno potrebbe avere già scritto questo codice TODO che può condividere ???

Grazie!

+0

Il legame è rotto. Puoi aggiornarlo. – Kayser

+0

Sei sicuro di voler fare una copia profonda? Ciò potrebbe portare a duplicare l'intero database. Preferisco attenermi all'attuazione della copia - noioso, ma potrei salvarmi un mal di testa o peggio, un server che si blocca in produzione. –

risposta

2

Sono stato in grado di ottenere una copia approfondita per lavorare come descritto nella domanda. È necessario caricare avidamente l'intero grafico e ripristinare gli ID @ su zero o zero. Ho scoperto che Hibernate SessionFactory ha effettivamente dei metodi per aiutare con questo processo.

Le altre raccomandazioni di cui sopra per le copie profonde non sembra funzionare. Certo, il problema potrebbe essere stato tra tastiera e sedia. Ma sta funzionando ora.

Grazie a tutti!

+4

Fondamentalmente hai detto che dobbiamo caricare avidamente tutto il grafico, ma non spesso il nostro grafico ha delle inizializzazioni pigri. Hai anche commentato che SessionFactory ha metodi per aiutare il processo a resettare gli @ id. Puoi fornire qualche esempio di codice, perché sto affrontando lo stesso problema e mi sta facendo impazzire :) –

1

Perché vuoi farlo? Sembra un po 'come l'hacking.

Detto Apache Commons BeanUtils contiene cloneBean() e copyProperties() metodi per rendere (poco profonde) copie di oggetti. Per fare una copia profonda puoi scrivere un metodo come proposto here.

+0

Voglio fare copie profonde dei dati memorizzati nel database che saranno completamente indipendenti dall'oggetto da cui sono stati copiati. Ad esempio: -Oggetto1 è una copia profonda di Object2. -Oggetto1 ha un figlio (da @OneToMany) che cambia. - Il bambino di Object2 non dovrebbe cambiare. – User1

+2

Per deepCopy è possibile utilizzare SerializationUtils.clone() da apache commons – rozky

3

Non sono davvero sicuro se l'azzeramento degli ID di oggetti già gestiti sia una buona idea, esp. quando le entità non hanno equals() definito come uguaglianza di ID. L'implementazione JPA avrebbe potuto avere gli oggetti gestiti in qualche cache e diventare inutile quando giocava con gli ID degli oggetti presenti.

Credo che sarebbe più sicuro seguire la risposta di R.K. e fare la vera copia di oggetti.

+0

concordato. Ho lavorato su un paio di progetti in cui abbiamo cercato di trovare un modo generalizzato per fare una copia profonda di un oggetto grafico. Il problema che si incontra sempre mentre il progetto cresce è che si finisce per dover copiare parti diverse del grafico dell'oggetto per diversi casi d'uso e/o diversi gruppi di proprietà all'interno di un oggetto. Alla fine, è più facile scrivere semplicemente la logica da soli piuttosto che cercare di diventare intelligenti con la clonazione automatica profonda. Inoltre, la cancellazione degli ID quasi sicuramente infrange alcune implementazioni JPA. –

2

Se gli oggetti implementano Serializable, è possibile utilizzare writeObject() e readObject() per eseguire una copia profonda. Abbiamo una gerarchia di oggetti di trasferimento dati e supporto copie profonde tramite questo metodo nella superclasse astratta (DTO):

/** 
* Reply a deep copy of this DTO. This generic method works for any DTO subclass: 
* 
*  Person person = new Person(); 
*  Person copy = person.deepCopy(); 
* 
* Note: Using Java serialization is easy, but can be expensive. Use with care. 
* 
* @return A deep copy of this DTO. 
*/ 
@SuppressWarnings("unchecked") 
public <T extends DTO> T deepCopy() 
{ 
    try 
    { 
     ObjectOutputStream oos = null; 
     ObjectInputStream ois = null; 
     try 
     { 
      ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
      oos = new ObjectOutputStream(bos); 
      oos.writeObject(this); 
      oos.flush(); 
      ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())); 
      return (T) ois.readObject(); 
     } 
     finally 
     { 
      oos.close(); 
      ois.close(); 
     } 
    } 
    catch (ClassNotFoundException cnfe) 
    { 
     // Impossible, since both sides deal in the same loaded classes. 
     return null; 
    } 
    catch (IOException ioe) 
    { 
     // This has to be "impossible", given that oos and ois wrap a *byte array*. 
     return null; 
    } 
} 

(. Sono sicuro che qualcuno troverà un motivo per cui si possono verificare queste eccezioni )

Altre librerie di serializzazione (ad es. XStream) potrebbero essere utilizzate allo stesso modo.

+3

Questo non farà una copia dei campi contrassegnati con la parola chiave transitoria. –

+0

@Sean: Grazie, un buon punto. (Nel nostro caso si tratta di JavaBeans vanigliati.) –

+2

@Sean: Poiché i campi transitori non finiranno nel DB, anche questo sembra essere il comportamento corretto. –

3

Ho risolto questo.

Ho creato un componente che rende l'intero processo per te basato sulle annotazioni del pacchetto (javax.persistence).

Il componente imposta già l'id dell'entità su null. Effettua tutte le analisi dell'algoritmo da applicare in base al tipo di ciascuna relazione di attributo @OneToMany, @OneToOne o @ManyToMany.

Esempio

Person person = personDAO.find(1); 
PersistenceCloner cloner = new PersistenceCloner(person); 
Person personCopy = cloner.generateCopyToPersist(); 

Scarica JAR e fonti: jpa-entitycloner