EntityManager.merge()
può inserire nuovi oggetti e aggiornare quelli esistenti.
Perché uno dovrebbe voler usare persist()
(che può solo creare nuovi oggetti)?
EntityManager.merge()
può inserire nuovi oggetti e aggiornare quelli esistenti.
Perché uno dovrebbe voler usare persist()
(che può solo creare nuovi oggetti)?
Risposte:
In entrambi i casi si aggiungerà un'entità a un PersistenceContext, la differenza è in ciò che si fa con l'entità in seguito.
Persist accetta un'istanza di entità, la aggiunge al contesto e rende tale istanza gestita (ovvero verranno tracciati i futuri aggiornamenti dell'entità).
Unisci restituisce l'istanza gestita in cui è stato unito lo stato. Restituisce qualcosa che esiste in PersistenceContext o crea una nuova istanza della tua entità. In ogni caso, copierà lo stato dall'entità fornita e restituirà la copia gestita. L'istanza trasmessa non verrà gestita (eventuali modifiche apportate non faranno parte della transazione, a meno che non si chiami nuovamente unione). È possibile utilizzare l'istanza restituita (gestita).
Forse un esempio di codice sarà di aiuto.
MyEntity e = new MyEntity();
// scenario 1
// tran starts
em.persist(e);
e.setSomeField(someValue);
// tran ends, and the row for someField is updated in the database
// scenario 2
// tran starts
e = new MyEntity();
em.merge(e);
e.setSomeField(anotherValue);
// tran ends but the row for someField is not updated in the database
// (you made the changes *after* merging)
// scenario 3
// tran starts
e = new MyEntity();
MyEntity e2 = em.merge(e);
e2.setSomeField(anotherValue);
// tran ends and the row for someField is updated
// (the changes were made to e2, not e)
Gli scenari 1 e 3 sono approssimativamente equivalenti, ma ci sono alcune situazioni in cui si desidera utilizzare lo scenario 2.
merge
la copia completa di un oggetto prima di gestirlo ha un impatto sulle prestazioni?
@GeneratedId
posso ottenerlo nello scenario 2?
Persistenza e unione hanno due scopi diversi (non sono affatto alternative).
(modificato per espandere le informazioni sulle differenze)
persistere:
merge:
efficienza persist ():
semantica persist ():
Esempio:
{
AnyEntity newEntity;
AnyEntity nonAttachedEntity;
AnyEntity attachedEntity;
// Create a new entity and persist it
newEntity = new AnyEntity();
em.persist(newEntity);
// Save 1 to the database at next flush
newEntity.setValue(1);
// Create a new entity with the same Id than the persisted one.
AnyEntity nonAttachedEntity = new AnyEntity();
nonAttachedEntity.setId(newEntity.getId());
// Save 2 to the database at next flush instead of 1!!!
nonAttachedEntity.setValue(2);
attachedEntity = em.merge(nonAttachedEntity);
// This condition returns true
// merge has found the already attached object (newEntity) and returns it.
if(attachedEntity==newEntity) {
System.out.print("They are the same object!");
}
// Set 3 to value
attachedEntity.setValue(3);
// Really, now both are the same object. Prints 3
System.out.println(newEntity.getValue());
// Modify the un attached object has no effect to the entity manager
// nor to the other objects
nonAttachedEntity.setValue(42);
}
In questo modo esiste solo 1 oggetto allegato per qualsiasi registro nel gestore entità.
merge () per un'entità con un ID è simile a:
AnyEntity myMerge(AnyEntity entityToSave) {
AnyEntity attached = em.find(AnyEntity.class, entityToSave.getId());
if(attached==null) {
attached = new AnyEntity();
em.persist(attached);
}
BeanUtils.copyProperties(attached, entityToSave);
return attached;
}
Sebbene se connesso a MySQL merge () potrebbe essere efficiente come persist () utilizzando una chiamata a INSERT con l'opzione ON DUPLICATE KEY UPDATE, JPA è una programmazione di livello molto alto e non si può presumere che sarà così ovunque.
em.persist(x)
con x = em.merge(x)
?
merge()
può anche lanciare unEntityExistsException
RuntimeException
, ma non è menzionato nel Javadoc.
Se si utilizza il generatore assegnato, l' utilizzo di merge invece di persist può causare un'istruzione SQL ridondante , influendo quindi sulle prestazioni.
Inoltre, chiamare l'unione per entità gestite è anche un errore poiché le entità gestite sono gestite automaticamente da Hibernate e il loro stato è sincronizzato con il record del database dal meccanismo di controllo sporco al momento dello svuotamento del contesto di persistenza .
Per capire come funziona tutto ciò, dovresti prima sapere che Hibernate sposta la mentalità degli sviluppatori dalle istruzioni SQL alle transizioni di stato delle entità .
Una volta che un'entità è attivamente gestita da Hibernate, tutte le modifiche verranno automaticamente propagate al database.
Hibernate monitora le entità attualmente collegate. Ma affinché un'entità venga gestita, deve trovarsi nello stato entità corretto.
Per comprendere meglio le transizioni di stato JPA, è possibile visualizzare il diagramma seguente:
O se usi l'API specifica di Hibernate:
Come illustrato dai diagrammi precedenti, un'entità può trovarsi in uno dei seguenti quattro stati:
Nuovo (Transitorio)
Un oggetto appena creato che non è mai stato associato a un Hibernate Session
(akaPersistence Context
) e non è mappato su nessuna riga della tabella del database è considerato nello stato Nuovo (Transitorio).
Per perseverare, è necessario chiamare esplicitamente il EntityManager#persist
metodo o utilizzare il meccanismo di persistenza transitiva.
Persistente (gestito)
Un'entità persistente è stata associata a una riga della tabella del database ed è gestita dal contesto di persistenza attualmente in esecuzione. Qualsiasi modifica apportata a tale entità verrà rilevata e propagata al database (durante il flush-time della sessione). Con Hibernate, non è più necessario eseguire le istruzioni INSERT / UPDATE / DELETE. Hibernate utilizza uno stile di lavoro transazionale write-behind e le modifiche sono sincronizzate all'ultimo momento responsabile, durante l'attuale Session
flush-time.
Distaccato
Una volta chiuso il contesto di persistenza attualmente in esecuzione, tutte le entità gestite in precedenza vengono staccate. Le modifiche successive non verranno più monitorate e non verrà eseguita alcuna sincronizzazione automatica del database.
Per associare un'entità staccata a una Sessione di ibernazione attiva, è possibile scegliere una delle seguenti opzioni:
ricollegamento
Hibernate (ma non JPA 2.1) supporta il ricollegamento tramite il metodo di aggiornamento Session #. Una sessione di ibernazione può associare solo un oggetto Entity per una determinata riga del database. Questo perché il contesto di persistenza funge da cache in memoria (cache di primo livello) e solo un valore (entità) è associato a una determinata chiave (tipo di entità e identificativo del database). Un'entità può essere ricollegata solo se non esiste nessun altro oggetto JVM (corrispondente alla stessa riga del database) già associato alla sessione di ibernazione corrente.
Fusione
L'unione sta per copiare lo stato di entità staccata (origine) in un'istanza di entità gestita (destinazione). Se l'entità risultante dalla fusione non ha equivalenti nella sessione corrente, ne verrà recuperata una dal database. L'istanza dell'oggetto disconnesso continuerà a rimanere staccata anche dopo l'operazione di unione.
Rimosso
Sebbene l'APP richieda che solo le entità gestite possano essere rimosse, Hibernate può anche eliminare entità distaccate (ma solo tramite una chiamata al metodo di eliminazione Session #). Un'entità rimossa viene pianificata solo per l'eliminazione e l'istruzione DELETE effettiva del database verrà eseguita durante il flush-time della sessione.
Ho notato che quando l'ho usato em.merge
, ho ricevuto una SELECT
dichiarazione per tutti INSERT
, anche quando non c'era campo che JPA stava generando per me - il campo chiave principale era un UUID che mi ero impostato. Sono passato a em.persist(myEntityObject)
e ho ottenuto solo INSERT
dichiarazioni allora.
merge()
. Avevo il database PostgreSQL con una vista complicata : la vista aggregava i dati da più tabelle (le tabelle avevano una struttura identica ma nomi diversi). Quindi JPA ha provato a farlo merge()
, ma in realtà JPA è stato inizialmente creato SELECT
(il database a causa delle impostazioni di visualizzazione potrebbe restituire diversi record con la stessa chiave primaria da tabelle diverse!), Quindi JPA (Hibernate era un implementatiion) fallito: ci sono diversi record con la stessa chiave ( org.hibernate.HibernateException: More than one row with the given identifier was found
). Nel mio caso persist()
mi ha aiutato.
La specifica JPA dice quanto segue persist()
.
Se X è un oggetto distaccato,
EntityExistsException
può essere lanciato quando viene invocata l'operazione persist o l'unoEntityExistsException
o l'altroPersistenceException
può essere lanciato al momento del flush o del commit.
Quindi l'uso persist()
sarebbe adatto quando l'oggetto non dovrebbe essere un oggetto distaccato. Potresti preferire che il codice PersistenceException
venga lanciato in modo che fallisca rapidamente.
Sebbene le specifiche non siano chiare , è persist()
possibile impostare l' @GeneratedValue
@Id
oggetto per un oggetto. merge()
tuttavia deve avere un oggetto con il @Id
già generato.
merge()
tuttavia deve avere un oggetto con il @Id
già generato . ". Ogni volta che EntityManager non trova un valore per il campo ID oggetto, viene persistito (inserito) nel DB.
Alcuni ulteriori dettagli sull'unione che ti aiuteranno a utilizzare l'unione su persist:
La restituzione di un'istanza gestita diversa dall'entità originale è una parte fondamentale del processo di unione. Se un'istanza di entità con lo stesso identificatore esiste già nel contesto di persistenza, il provider sovrascriverà il suo stato con lo stato dell'entità che viene unita, ma la versione gestita che esisteva già deve essere restituita al client in modo che possa essere Usato. Se il provider non ha aggiornato l'istanza Employee nel contesto di persistenza, eventuali riferimenti a tale istanza diventeranno incoerenti con il nuovo stato che viene unito.
Quando merge () viene invocato su una nuova entità, si comporta in modo simile all'operazione persist (). Aggiunge l'entità al contesto di persistenza, ma invece di aggiungere l'istanza dell'entità originale, crea una nuova copia e gestisce invece quell'istanza. La copia creata dall'operazione merge () viene mantenuta come se su di essa fosse stato invocato il metodo persist ().
In presenza di relazioni, l'operazione merge () tenterà di aggiornare l'entità gestita in modo che punti alle versioni gestite delle entità a cui fa riferimento l'entità staccata. Se l'entità ha una relazione con un oggetto che non ha identità persistente, il risultato dell'operazione di unione non è definito. Alcuni provider potrebbero consentire alla copia gestita di puntare all'oggetto non persistente, mentre altri potrebbero generare immediatamente un'eccezione. L'operazione merge () può essere facoltativamente messa in cascata in questi casi per evitare che si verifichi un'eccezione. Tratteremo in cascata dell'operazione merge () più avanti in questa sezione. Se un'entità che viene unita punta a un'entità rimossa, verrà generata un'eccezione IllegalArgumentException.
Le relazioni di caricamento lento sono un caso speciale nell'operazione di unione. Se una relazione a caricamento lento non è stata attivata su un'entità prima che si staccasse, tale relazione verrà ignorata quando l'entità viene unita. Se la relazione è stata attivata mentre gestita e quindi impostata su null mentre l'entità era staccata, anche la versione gestita dell'entità avrà la relazione cancellata durante l'unione. "
Tutte le informazioni di cui sopra sono state tratte da "Pro JPA 2 Mastering the Java ™ Persistence API" di Mike Keith e Merrick Schnicariol. Capitolo 6. Separazione e fusione della sezione. Questo libro è in realtà un secondo libro dedicato all'APP dagli autori. Questo nuovo libro contiene molte nuove informazioni rispetto al precedente. Ho davvero raccomandato di leggere questo libro per coloro che saranno seriamente coinvolti con l'APP. Mi dispiace per aver pubblicato anonimamente la mia prima risposta.
Ci sono alcune differenze tra merge
e persist
(enumererò di nuovo quelle già postate qui):
D1. merge
non gestisce l'entità passata, ma restituisce piuttosto un'altra istanza gestita. persist
dall'altro lato renderà gestita l'entità passata:
//MERGE: passedEntity remains unmanaged, but newEntity will be managed
Entity newEntity = em.merge(passedEntity);
//PERSIST: passedEntity will be managed after this
em.persist(passedEntity);
D2. Se rimuovi un'entità e poi decidi di mantenere l'entità indietro, puoi farlo solo con persist (), perché merge
genererà unIllegalArgumentException
.
D3. Se hai deciso di occuparti manualmente dei tuoi ID (ad es. Utilizzando UUID), merge
un'operazione attiverà le SELECT
query successive al fine di cercare entità esistenti con quell'ID, mentrepersist
potrebbero non essere necessarie quelle query.
D4. Ci sono casi in cui semplicemente non ti fidi del codice che chiama il tuo codice e per assicurarti che nessun dato sia aggiornato, ma piuttosto inserito, devi usare persist
.
Stavo diventando pigroCaricando eccezioni sulla mia entità perché stavo provando ad accedere a una raccolta caricata pigra che era in sessione.
Quello che avrei fatto sarebbe stato in una richiesta separata, recuperare l'entità dalla sessione e quindi provare ad accedere a una raccolta nella mia pagina jsp che era problematica.
Per ovviare a questo, ho aggiornato la stessa entità nel mio controller e l'ho passata al mio jsp, anche se immagino che quando avrò salvato di nuovo in sessione che sarà accessibile anche se SessionScope
e non lancio a LazyLoadingException
, una modifica dell'esempio 2:
Per me ha funzionato:
// scenario 2 MY WAY
// tran starts
e = new MyEntity();
e = em.merge(e); // re-assign to the same entity "e"
//access e from jsp and it will work dandy!!
Ho trovato questa spiegazione dai documenti di Hibernate illuminante, perché contengono un caso d'uso:
L'uso e la semantica di merge () sembra essere fonte di confusione per i nuovi utenti. Innanzitutto, finché non si tenta di utilizzare lo stato oggetto caricato in un gestore entità in un altro nuovo gestore entità, non è necessario utilizzare merge () . Alcune intere applicazioni non useranno mai questo metodo.
Di solito merge () viene utilizzato nel seguente scenario:
- L'applicazione carica un oggetto nel primo gestore entità
- l'oggetto viene passato al livello di presentazione
- vengono apportate alcune modifiche all'oggetto
- l'oggetto viene restituito al livello della logica aziendale
- l'applicazione persiste queste modifiche chiamando merge () in un secondo gestore entità
Ecco l'esatta semantica di merge ():
- se esiste un'istanza gestita con lo stesso identificatore attualmente associato al contesto di persistenza, copiare lo stato dell'oggetto specificato sull'istanza gestita
- se non esiste un'istanza gestita attualmente associata al contesto di persistenza, provare a caricarla dal database o creare una nuova istanza gestita
- viene restituita l'istanza gestita
- l'istanza data non viene associata al contesto di persistenza, rimane distaccata e di solito viene scartata
Da: http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html
Scorrendo le risposte mancano alcuni dettagli su `Cascade 'e la generazione dell'id. Vedi la domanda
Inoltre, vale la pena ricordare che è possibile avere Cascade
annotazioni separate per l'unione e la persistenza: Cascade.MERGE
eCascade.PERSIST
che verranno trattate secondo il metodo utilizzato.
La specifica è tua amica;)
JPA è indiscutibilmente una grande semplificazione nel dominio delle applicazioni aziendali costruite sulla piattaforma Java. Come sviluppatore che ha dovuto far fronte alle complessità dei vecchi bean di entità in J2EE, vedo l'inclusione di JPA tra le specifiche Java EE come un grande passo avanti. Tuttavia, mentre approfondisco i dettagli dell'APP, trovo cose che non sono così facili. In questo articolo mi occupo del confronto tra i metodi di unione e persistenza di EntityManager il cui comportamento sovrapposto può causare confusione non solo a un principiante. Inoltre, propongo una generalizzazione che vede entrambi i metodi come casi speciali di un metodo più generale combinato.
Entità persistenti
A differenza del metodo di unione, il metodo persist è piuttosto semplice e intuitivo. Lo scenario più comune dell'uso del metodo persist può essere riassunto come segue:
"Un'istanza appena creata della classe di entità viene passata al metodo persist. Dopo la restituzione di questo metodo, l'entità viene gestita e pianificata per l'inserimento nel database. Può accadere durante o prima del commit della transazione o quando viene chiamato il metodo flush. Se l'entità fa riferimento a un'altra entità attraverso una relazione contrassegnata con la strategia a cascata PERSIST, anche questa procedura viene applicata ad essa. "
La specifica va più nei dettagli, tuttavia, ricordarli non è cruciale in quanto questi dettagli coprono solo situazioni più o meno esotiche.
Unione di entità
In confronto a persistere, la descrizione del comportamento della fusione non è così semplice. Non esiste uno scenario principale, come nel caso di persist, e un programmatore deve ricordare tutti gli scenari per poter scrivere un codice corretto. Mi sembra che i progettisti dell'APP volessero avere un metodo la cui preoccupazione principale sarebbe la gestione di entità distaccate (come l'opposto del metodo persist che si occupa principalmente di entità appena create). Il compito principale del metodo di unione è trasferire lo stato da un entità non gestita (passata come argomento) alla sua controparte gestita nel contesto di persistenza. Questo compito, tuttavia, si divide ulteriormente in diversi scenari che peggiorano l'intelligibilità del comportamento complessivo del metodo.
Invece di ripetere i paragrafi dalla specifica JPA, ho preparato un diagramma di flusso che illustra schematicamente il comportamento del metodo di unione:
Quindi, quando dovrei usare persist e quando unire?
persistere
merge
Scenario X:
Tabella: Spitter (uno), Tabella: Spittles (molti) (Spittles è il proprietario della relazione con un FK: spitter_id)
Questo scenario comporta un risparmio: The Spitter ed entrambi Spittles come se fossero di proprietà di Same Spitter.
Spitter spitter=new Spitter();
Spittle spittle3=new Spittle();
spitter.setUsername("George");
spitter.setPassword("test1234");
spittle3.setSpittle("I love java 2");
spittle3.setSpitter(spitter);
dao.addSpittle(spittle3); // <--persist
Spittle spittle=new Spittle();
spittle.setSpittle("I love java");
spittle.setSpitter(spitter);
dao.saveSpittle(spittle); //<-- merge!!
Scenario Y:
Questo salverà lo Spitter, salverà i 2 Spittles Ma non faranno riferimento allo stesso Spitter!
Spitter spitter=new Spitter();
Spittle spittle3=new Spittle();
spitter.setUsername("George");
spitter.setPassword("test1234");
spittle3.setSpittle("I love java 2");
spittle3.setSpitter(spitter);
dao.save(spittle3); // <--merge!!
Spittle spittle=new Spittle();
spittle.setSpittle("I love java");
spittle.setSpitter(spitter);
dao.saveSpittle(spittle); //<-- merge!!
Un'altra osservazione:
merge()
si preoccuperà solo di un ID generato automaticamente (testato su IDENTITY
e SEQUENCE
) quando nella tabella esiste già un record con tale ID. In tal caso merge()
proverà ad aggiornare il record. Se, tuttavia, un ID è assente o non corrisponde a nessun record esistente, merge()
lo ignorerà completamente e chiederà a un db di allocarne uno nuovo. Questo a volte è fonte di molti bug. Non utilizzare merge()
per forzare un ID per un nuovo record.
persist()
d'altra parte non ti permetterà nemmeno di passarci un ID. Fallirà immediatamente. Nel mio caso, è:
Causato da: org.hibernate.PersistentObjectException: entità distaccata passata a persistere
hibernate-jpa javadoc ha un suggerimento:
Produce : javax.persistence.EntityExistsException - se l'entità esiste già. (Se l'entità esiste già, l'EntityExistsException può essere generata quando viene invocata l'operazione persist, oppure l'EntityExistsException o un'altra PersistenceException può essere lanciata al momento del flush o del commit.)
persist()
non si lamenterà che ha un ID, si lamenta solo quando nel database è già presente qualcosa con lo stesso ID.
Potresti essere venuto qui per un consiglio su quando usare persist e quando usare merge . Penso che dipenda dalla situazione: quanto è probabile che sia necessario creare un nuovo record e quanto sia difficile recuperare i dati persistenti.
Supponiamo che tu possa usare una chiave / identificatore naturale.
I dati devono essere persistenti, ma di tanto in tanto esiste un record e viene richiesto un aggiornamento. In questo caso puoi provare a persistere e se genera un EntityExistsException, lo cerchi e combini i dati:
provare {entityManager.persist (entity)}
catch (eccezione EntityExistsException) {/ * recupera e unisci * /}
I dati persistenti devono essere aggiornati, ma di tanto in tanto non esiste ancora alcun record per i dati. In questo caso lo cerchi e fai una persistenza se l'entità manca:
entity = entityManager.find (chiave);
if (entity == null) {entityManager.persist (entity); }
altrimenti {/ * merge * /}
Se non hai la chiave / identificatore naturale, avrai più difficoltà a capire se l'entità esiste o no, o come cercarla.
Le fusioni possono essere gestite anche in due modi:
persist (entità) dovrebbe essere usato con entità totalmente nuove, per aggiungerle al DB (se l'entità esiste già nel DB ci sarà il lancio di EntityExistsException).
merge (entity) dovrebbe essere usato, per riportare l'entità in un contesto di persistenza se l'entità è stata staccata ed è stata cambiata.
Probabilmente persistere sta generando l'istruzione sql INSERT e unendo l'istruzione sql UPDATE (ma non ne sono sicuro).