JPA EntityManager: Perché usare persist () over merge ()?


Answers

Persistere e fondersi sono per due scopi diversi (non sono affatto alternative).

(modificato per espandere le informazioni sulle differenze)

persistere:

  • Inserire un nuovo registro nel database
  • Allegare l'oggetto al gestore entità.

merge:

  • Trova un oggetto collegato con lo stesso ID e aggiornalo.
  • Se esiste, aggiorna e restituisce l'oggetto già associato.
  • Se non esiste inserisci il nuovo registro nel database.

persist () efficienza:

  • Potrebbe essere più efficiente inserire un nuovo registro in un database piuttosto che unire ().
  • Non duplica l'oggetto originale.

semantica persist ():

  • Si assicura che tu stia inserendo e non aggiornando per errore.

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

unire () per un'entità con un id è qualcosa del tipo:

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

Anche se connesso a MySQL merge () potrebbe essere efficiente quanto persist () utilizzando una chiamata a INSERT con l'opzione ON DUPLICATE KEY UPDATE, JPA è una programmazione di altissimo livello e non si può presumere che questo avvenga ovunque.

Question

EntityManager.merge() può inserire nuovi oggetti e aggiornare quelli esistenti.

Perché si vorrebbe usare persist() (che può solo creare nuovi oggetti)?




Ho notato che quando ho usato em.merge , ho ottenuto un'istruzione SELECT per ogni INSERT , anche quando non c'era alcun campo generato da JPA per me: il campo chiave primaria era un UUID che mi ero prefissato. Sono passato a em.persist(myEntityObject) e ho ottenuto solo istruzioni INSERT quindi.




JPA è indiscutibilmente una grande semplificazione nel dominio delle applicazioni aziendali basate 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 balzo in avanti. Tuttavia, mentre approfondisco i dettagli del JPA, trovo cose che non sono così semplici. In questo articolo mi occupo del confronto tra i metodi di unione e persistenza di EntityManager il cui comportamento di sovrapposizione potrebbe causare confusione non solo per un principiante. Inoltre propongo una generalizzazione che vede entrambi i metodi come casi speciali di un metodo più generale combinato.

Entità persistenti

In contrasto con il 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 entity viene passata al metodo persist: dopo che questo metodo viene restituito, l'entità viene gestita e pianificata per l'inserimento nel database e può accadere prima o prima dell'impegno 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 cascade PERSIST, anche questa procedura viene applicata ad essa. "

Le specifiche sono più dettagliate, tuttavia, ricordarle non è cruciale in quanto questi dettagli riguardano solo situazioni più o meno esotiche.

Entità di unione

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 scrivere un codice corretto. Mi sembra che i progettisti JPA volessero avere un metodo il cui principale interesse sarebbe gestire entità distaccate (come l'opposto del metodo persist che si occupa principalmente delle entità appena create). Il compito principale del metodo di unione è quello di trasferire lo stato da un entità non gestita (passata come argomento) alla sua controparte gestita all'interno del contesto di persistenza. Questo compito, tuttavia, si divide ulteriormente in diversi scenari che peggiorano l'intelligibilità del comportamento del metodo generale.

Invece di ripetere paragrafi dalla specifica JPA, ho preparato un diagramma di flusso che descrive schematicamente il comportamento del metodo di unione:

Quindi, quando dovrei usare persist e quando si uniscono?

persistere

  • Vuoi che il metodo crei sempre una nuova entità e non aggiorni mai un'entità. In caso contrario, il metodo genera un'eccezione come conseguenza della violazione dell'unicità della chiave primaria.
  • Processi batch, gestione delle entità in modo statico (vedere Schema gateway).
  • Ottimizzazione delle prestazioni

fondersi

  • Si desidera che il metodo inserisca o aggiorni un'entità nel database.
  • Si desidera gestire le entità in modo stateless (oggetti di trasferimento dati nei servizi)
  • Si desidera inserire una nuova entità che potrebbe avere un riferimento a un'altra entità che potrebbe ma non essere ancora creata (la relazione deve essere contrassegnata MERGE). Ad esempio, inserendo una nuova foto con un riferimento a un album nuovo o preesistente.



Alcuni ulteriori dettagli sull'unione che ti aiuteranno a utilizzare l'unione persistono:

Il ritorno di un'istanza gestita diversa dall'entità originale è una parte critica 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 già esistente deve essere restituita al client in modo che possa essere Usato. Se il provider non ha aggiornato l'istanza Employee nel contesto di persistenza, qualsiasi riferimento a tale istanza diventerà incoerente con il nuovo stato di unione.

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 di entità originale, crea una nuova copia e gestisce invece tale istanza. La copia creata dall'operazione merge () viene mantenuta come se il metodo persist () fosse invocato su di esso.

In presenza di relazioni, l'operazione di unione () tenterà di aggiornare l'entità gestita in modo che punti alle versioni gestite delle entità a cui fa riferimento l'entità distaccata. 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 di unione () può essere facoltativamente sovrapposta in questi casi per impedire che si verifichi un'eccezione. Copriremo in cascata l'operazione di unione () 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 lazy-loading non è stata attivata su un'entità prima che venisse rimossa, quella relazione verrà ignorata quando l'entità viene unita. Se la relazione è stata attivata durante la gestione e quindi impostata su null mentre l'entità è stata scollegata, anche la versione gestita dell'entità avrà la relazione cancellata durante l'unione. "

Tutte le informazioni di cui sopra sono state prese da "Pro JPA 2 Mastering Java ™ Persistence API" di Mike Keith e Merrick Schnicariol. Capitolo 6. Separazione delle sezioni e fusione. Questo libro è in realtà un secondo libro dedicato all'APP dagli autori. Questo nuovo libro ha molte nuove informazioni quindi precedenti. Ho davvero raccomandato di leggere questo libro per quelli che saranno seriamente coinvolti con JPA. Mi dispiace per aver pubblicato anonimamente la mia prima risposta.




Scenario X:

Tabella: Spitter (uno), Tabella: Spittles (molti) (Spittles è il proprietario della relazione con un FK: spitter_id)

Questo scenario si traduce in un risparmio: 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!!



Stavo diventando pigroLoading eccezioni sulla mia entità perché stavo cercando di accedere a una raccolta lazy caricato che era in sessione.

Quello che avrei fatto era 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 alleggerirlo, ho aggiornato la stessa entità nel mio controller e l'ho passata al mio jsp, anche se immagino che quando ho ri-salvato in sessione sia anche accessibile tramite SessionScope e non LazyLoadingException una LazyLoadingException , una modifica dell'esempio 2:

Quanto segue ha funzionato per me:

// 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!!



Potresti essere venuto qui per un consiglio su quando usare persist e quando usare l' unione . 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 mantenuti, ma una volta ogni tanto esiste un record e viene richiesto un aggiornamento. In questo caso puoi provare un persist e se lancia una EntityExistsException, la cerchi e combina i dati:

    prova {entityManager.persist (entity)}

    catch (eccezione EntityExistsException) {/ * recuperare e unire * /}

  • I dati persistenti devono essere aggiornati, ma una volta ogni tanto non c'è ancora un record per i dati. In questo caso lo si cerca e si persiste se manca l'entità:

    entity = entityManager.find (chiave);

    if (entity == null) {entityManager.persist (entity); }

    else {/ * unione * /}

Se non hai la chiave / identificatore naturale, avrai più difficoltà a capire se l'entità esiste o no, o come cercarla.

Le unioni possono essere affrontate anche in due modi:

  1. Se le modifiche sono in genere ridotte, applicarle all'entità gestita.
  2. Se le modifiche sono comuni, copia l'ID dall'entità persistente, nonché i dati inalterati. Quindi chiama EntityManager :: merge () per sostituire il vecchio contenuto.