Quali sono le differenze tra i diversi metodi di salvataggio in Hibernate?


199

Hibernate ha una manciata di metodi che, in un modo o nell'altro, prendono l'oggetto e lo inseriscono nel database. Quali sono le differenze tra loro, quando usare quale e perché non esiste un solo metodo intelligente che sappia quando usare cosa?

I metodi che ho identificato finora sono:

  • save()
  • update()
  • saveOrUpdate()
  • saveOrUpdateCopy()
  • merge()
  • persist()

Risposte:


117

Ecco la mia comprensione dei metodi. Principalmente si basano sull'API, poiché in pratica non li uso tutti.

saveOrUpdate Le chiamate possono essere salvate o aggiornate in base ad alcuni controlli. Ad esempio, se non esiste un identificatore, viene chiamato save. Altrimenti viene chiamato l'aggiornamento.

salva Persiste un'entità. Assegnerà un identificatore se non esiste. Se lo fa, sta essenzialmente facendo un aggiornamento. Restituisce l'ID generato dell'entità.

update Tenta di persistere nell'entità utilizzando un identificatore esistente. Se non esiste un identificatore, credo che venga generata un'eccezione.

saveOrUpdateCopy Questo è obsoleto e non dovrebbe più essere utilizzato. Invece c'è ...

Unisci Ora è qui che la mia conoscenza inizia a vacillare. La cosa importante qui è la differenza tra entità transitorie, distaccate e persistenti. Per maggiori informazioni sugli stati degli oggetti, dai un'occhiata qui . Con il salvataggio e l'aggiornamento, hai a che fare con oggetti persistenti. Sono collegati a una sessione, quindi Hibernate sa cosa è cambiato. Ma quando hai un oggetto transitorio, non c'è nessuna sessione coinvolta. In questi casi è necessario utilizzare l'unione per gli aggiornamenti e persistere per il salvataggio.

persistere Come accennato in precedenza, questo viene utilizzato su oggetti transitori. Non restituisce l'ID generato.


22
Vorrei accettarlo come risposta, ma una cosa è ancora poco chiara: poiché save () ricade sull'aggiornamento (), se tale elemento esiste, in cosa differisce da saveOrUpdate () nella pratica?
Henrik Paul,

Dove viene specificato, quel salvataggio funzionerebbe su istanze distaccate?
jrudolph,

2
Se la tua descrizione di unione / persistenza è importante solo su oggetti transitori, questo ha un senso e si adatta a come usiamo l'ibernazione. Si noti inoltre che un'unione presenta spesso limiti di prestazioni rispetto a un aggiornamento in quanto sembra eseguire un ulteriore recupero per i controlli di integrità di qualche tipo.
Martin Dale Lyness,

1
La risposta di jrudolph di seguito è più accurata.
Azerole,

2
Dato l'ibernazione probabilmente sa in che stato si trova un oggetto, perché dobbiamo farlo manualmente quando scriviamo il programma. Dovrebbe esserci un solo metodo di salvataggio.
masterxilo,

116
╔══════════════╦═══════════════════════════════╦════════════════════════════════╗
    METHOD                TRANSIENT                      DETACHED            
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                     sets id if doesn't         sets new id even if object   
    save()         exist, persists to db,        already has it, persists    
                  returns attached object     to DB, returns attached object 
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                     sets id on object                    throws             
   persist()       persists object to DB            PersistenceException     
                                                                             
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                                                                             
   update()              Exception                persists and reattaches    
                                                                             
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                copy the state of object in      copy the state of obj in    
    merge()        DB, doesn't attach it,    ║      DB, doesn't attach it,    
                  returns attached object         returns attached object    
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
                                                                             
saveOrUpdate()║           as save()                       as update()         
                                                                             
╚══════════════╩═══════════════════════════════╩════════════════════════════════╝

updateun oggetto transitorio va bene, non ho ricevuto un'eccezione.
GMsoF,

Quello che so, non possiamo persistere in alcun modo in un transitorio. Penso che la differenza potrebbe essere tra distaccato e persistente. Per favore, correggimi.
Ram

ci sono molti errori qui ... es. 1) ´save () ´ non restituisce un "oggetto attaccato", restituisce ´id´; 2) ´persist () ´ non è garantito per impostare ´id´, né "persiste l'oggetto su DB"; ...
Eugen Labun,

67
  • Vedi il Forum di Hibernate per una spiegazione delle sottili differenze tra persistere e salvare. Sembra che la differenza sia il momento in cui l'istruzione INSERT viene eseguita alla fine. Poiché save restituisce l'identificatore, l'istruzione INSERT deve essere eseguita istantaneamente indipendentemente dallo stato della transazione (che generalmente è una cosa negativa). Persist non eseguirà alcuna istruzione al di fuori della transazione attualmente in esecuzione solo per assegnare l'identificatore. Save / Persist funzionano entrambi su istanze transitorie , ovvero istanze a cui non è stato ancora assegnato un identificatore e come tali non vengono salvate nel DB.

  • Update e Merge funzionano entrambi su istanze distaccate , ovvero istanze che hanno una voce corrispondente nel DB ma che al momento non sono collegate a (o gestite da) una sessione. La differenza tra loro è ciò che accade all'istanza che viene passata alla funzione. update tenta di ricollegare l'istanza, ciò significa che non ci deve essere un'altra istanza dell'entità persistente collegata alla Session in questo momento, altrimenti viene generata un'eccezione. unione , tuttavia, copia solo tutti i valori in un'istanza persistente nella Sessione (che verrà caricata se non è attualmente caricata). L'oggetto di input non è cambiato. Quindi unisci è più generale dell'aggiornamento , ma potrebbe utilizzare più risorse.


L'istruzione insert continua a non accadere se si dispone del proprio generatore ID
kommradHomer,

Quindi, in caso di oggetto distaccato, l'unione attiverà una selezione mentre l'aggiornamento no?
Gab

1
save() - If an INSERT has to be executed to get the identifier, then this INSERT happens immediately, no matter if you are inside or outside of a transaction. This is problematic in a long-running conversation with an extended Session/persistence context.Potete per favore dirmi come può avvenire un inserto al di fuori di una sessione e perché è male?
Erran Morad,

Disclaimer: non uso l'ibernazione da molto tempo. IMO il problema è questo: la firma e il contratto di save () richiedono che save restituisca un identificatore per il nuovo oggetto. A seconda della strategia di generazione dell'id scelta, l'identificatore viene generato dal DB quando viene modificato un valore INSERT. Di conseguenza, in quei casi non è possibile restituire un identificatore in questo momento senza averlo generato e per generarlo è necessario eseguire INSERT subito . Dal momento che una transazione di lunga durata non viene eseguita in questo momento ma solo su commit, l'unico modo per eseguire INSERTora è eseguirla al di fuori di tx.
jrudolph,

12

Questo link spiega in modo corretto:

http://www.stevideter.com/2008/12/07/saveorupdate-versus-merge-in-hibernate/

Tutti abbiamo quei problemi che incontriamo abbastanza di rado che quando li vediamo di nuovo, sappiamo di averlo risolto, ma non ricordo come.

La NonUniqueObjectException generata quando si utilizza Session.saveOrUpdate () in Hibernate è una delle mie. Aggiungerò nuove funzionalità a un'applicazione complessa. Tutti i test delle mie unità funzionano bene. Quindi, testando l'interfaccia utente, cercando di salvare un oggetto, comincio a ottenere un'eccezione con il messaggio "un oggetto diverso con lo stesso valore identificativo era già associato alla sessione". Ecco alcuni esempi di codice da Java Persistence con Hibernate.

            Session session = sessionFactory1.openSession();
            Transaction tx = session.beginTransaction();
            Item item = (Item) session.get(Item.class, new Long(1234));
            tx.commit();
            session.close(); // end of first session, item is detached

            item.getId(); // The database identity is "1234"
            item.setDescription("my new description");
            Session session2 = sessionFactory.openSession();
            Transaction tx2 = session2.beginTransaction();
            Item item2 = (Item) session2.get(Item.class, new Long(1234));
            session2.update(item); // Throws NonUniqueObjectException
            tx2.commit();
            session2.close();

Per comprendere la causa di questa eccezione, è importante comprendere gli oggetti separati e cosa succede quando si chiama saveOrUpdate () (o semplicemente update ()) su un oggetto separato.

Quando chiudiamo una singola sessione di ibernazione, gli oggetti persistenti con cui stiamo lavorando vengono staccati. Ciò significa che i dati sono ancora nella memoria dell'applicazione, ma Hibernate non è più responsabile del rilevamento delle modifiche agli oggetti.

Se poi modifichiamo il nostro oggetto distaccato e vogliamo aggiornarlo, dobbiamo ricollegare l'oggetto. Durante quel processo di riattacco, Hibernate verificherà se ci sono altre copie dello stesso oggetto. Se ne trova qualcuno, deve dirci che non sa più quale sia la copia "reale". Forse sono state apportate altre modifiche a quelle altre copie che prevediamo di essere salvate, ma Hibernate non le conosce, perché non le gestiva al momento.

Invece di salvare dati potenzialmente errati, Hibernate ci informa del problema tramite NonUniqueObjectException.

Quindi cosa dobbiamo fare? In Hibernate 3, abbiamo merge () (in Hibernate 2, usa saveOrUpdateCopy ()). Questo metodo costringerà Hibernate a copiare eventuali modifiche da altre istanze distaccate sull'istanza che si desidera salvare e quindi unisce tutte le modifiche in memoria prima del salvataggio.

        Session session = sessionFactory1.openSession();
        Transaction tx = session.beginTransaction();
        Item item = (Item) session.get(Item.class, new Long(1234));
        tx.commit();
        session.close(); // end of first session, item is detached

        item.getId(); // The database identity is "1234"
        item.setDescription("my new description");
        Session session2 = sessionFactory.openSession();
        Transaction tx2 = session2.beginTransaction();
        Item item2 = (Item) session2.get(Item.class, new Long(1234));
        Item item3 = session2.merge(item); // Success!
        tx2.commit();
        session2.close();

È importante notare che l'unione restituisce un riferimento alla versione appena aggiornata dell'istanza. Non sta ricollegando l'oggetto alla Sessione. Se provi ad esempio l'uguaglianza (item == item3), troverai che in questo caso restituisce false. Probabilmente vorrai lavorare con item3 da questo punto in avanti.

È anche importante notare che l'API Java Persistence (JPA) non ha un concetto di oggetti distaccati e ricollegati e utilizza EntityManager.persist () e EntityManager.merge ().

Ho scoperto in generale che quando si utilizza Hibernate, saveOrUpdate () di solito è sufficiente per le mie esigenze. Di solito ho solo bisogno di usare l'unione quando ho oggetti che possono avere riferimenti a oggetti dello stesso tipo. Più recentemente, la causa dell'eccezione era nel codice che confermava che il riferimento non era ricorsivo. Stavo caricando lo stesso oggetto nella mia sessione come parte della convalida, causando l'errore.

Dove hai riscontrato questo problema? L'unione ha funzionato per te o hai bisogno di un'altra soluzione? Preferisci usare sempre l'unione o preferisci usarlo solo se necessario per casi specifici


Link all'articolo su webarchive, poiché l'originale non è disponibile: web.archive.org/web/20160521091122/http://www.stevideter.com:80/…
Eugen Labun,

5

In realtà la differenza tra ibernazione save()e persist()metodi dipende dalla classe del generatore che stiamo usando.

Se viene assegnata la nostra classe di generatori, non vi è alcuna differenza tra i metodi save()e persist(). Poiché il generatore 'assegnato' significa, come programmatore dobbiamo fornire il valore della chiave primaria per salvare nel giusto database [Spero che tu conosca questo concetto di generatori] Nel caso di una classe del generatore diversa da quella assegnata, supponi che se il nostro nome della classe del generatore è Increment significa ibernazione stessa assegnerà il valore dell'ID della chiave primaria nel database a destra [diverso dal generatore assegnato, l'ibernazione utilizzata solo per occuparsi del valore dell'ID della chiave primaria ricordare], quindi in questo caso se chiamiamo save()o persist()metodo inseriamo il record in il database normalmente Ma ascolta la cosa, il save()metodo può restituire quel valore ID chiave primaria che viene generato da ibernazione e possiamo vederlo da

long s = session.save(k);

In questo stesso caso, persist()non restituirà mai alcun valore al client.


5

Ho trovato un buon esempio che mostra le differenze tra tutti i metodi di salvataggio in sospensione:

http://www.journaldev.com/3481/hibernate-session-merge-vs-update-save-saveorupdate-persist-example

In breve, secondo il link sopra:

Salva()

  • Possiamo invocare questo metodo al di fuori di una transazione. Se lo utilizziamo senza transazione e abbiamo un collegamento in cascata tra entità, viene salvata solo l'entità primaria a meno che non eseguiamo lo svuotamento della sessione.
  • Quindi, se ci sono altri oggetti mappati dall'oggetto primario, vengono salvati al momento del commit della transazione o quando scarichiamo la sessione.

persistere()

  • È simile all'utilizzo di save () nelle transazioni, quindi è sicuro e si prende cura di tutti gli oggetti a cascata.

saveOrUpdate ()

  • Può essere usato con o senza la transazione, e proprio come save (), se usato senza la transazione, le entità mappate non verranno salvate alla fine della sessione.

  • Risultati in inserire o aggiornare le query in base ai dati forniti. Se i dati sono presenti nel database, viene eseguita la query di aggiornamento.

aggiornare()

  • L'aggiornamento ibernazione deve essere utilizzato laddove sappiamo che stiamo aggiornando solo le informazioni sull'entità. Questa operazione aggiunge l'oggetto entità al contesto persistente e ulteriori modifiche vengono tracciate e salvate quando viene eseguita la transazione.
  • Quindi, anche dopo aver chiamato update, se impostiamo dei valori nell'entità, questi verranno aggiornati al momento del commit della transazione.

merge ()

  • L'unione in sospensione può essere utilizzata per aggiornare i valori esistenti, tuttavia questo metodo crea una copia dall'oggetto entità passato e lo restituisce. L'oggetto restituito fa parte del contesto persistente e viene tracciato per eventuali modifiche, l'oggetto passato non viene tracciato. Questa è la principale differenza con merge () rispetto a tutti gli altri metodi.

Anche per esempi pratici di tutti questi, si prega di fare riferimento al link che ho menzionato sopra, mostra esempi per tutti questi diversi metodi.


3

Come ho spiegato in questo articolo , dovresti preferire i metodi JPA il più delle volte e le updateattività di elaborazione batch.

Un'entità JPA o Hibernate può trovarsi in uno dei seguenti quattro stati:

  • Transitorio (nuovo)
  • Gestito (persistente)
  • Distaccato
  • Rimosso (eliminato)

La transizione da uno stato all'altro avviene tramite i metodi EntityManager o Session.

Ad esempio, l'APP EntityManagerfornisce i seguenti metodi di transizione dello stato dell'entità.

inserisci qui la descrizione dell'immagine

Hibernate Sessionimplementa tutti i EntityManagermetodi JPA e fornisce alcuni metodi di transizione di stato entità aggiuntivi come save, saveOrUpdatee update.

inserisci qui la descrizione dell'immagine

Persistere

Per cambiare lo stato di un'entità da Transitorio (Nuovo) a Gestito (Persistito), possiamo usare il persistmetodo offerto dall'APP EntityManagerche è anche ereditato da Hibernate Session.

Il persistmetodo attiva un PersistEventche è gestito dal DefaultPersistEventListenerlistener di eventi Hibernate.

Pertanto, quando si esegue il seguente test case:

doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    LOGGER.info(
        "Persisting the Book entity with the id: {}", 
        book.getId()
    );
});

Hibernate genera le seguenti istruzioni SQL:

CALL NEXT VALUE FOR hibernate_sequence

-- Persisting the Book entity with the id: 1

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

Si noti che idviene assegnato prima di associare l' Bookentità al contesto di persistenza corrente. Ciò è necessario perché le entità gestite sono archiviate in una Mapstruttura in cui la chiave è formata dal tipo di entità e dal suo identificatore e il valore è il riferimento dell'entità. Questo è il motivo per cui l'APP EntityManagere l'ibernazione Sessionsono noti come cache di primo livello.

Quando chiama persist, l'entità è collegata solo al contesto di persistenza attualmente in esecuzione e INSERT può essere posticipato fino a quando non flushviene chiamato.

L'unica eccezione è il generatore di IDENTITÀ che attiva immediatamente INSERT poiché è l'unico modo per ottenere l'identificatore di entità. Per questo motivo, Hibernate non può inserire in batch inserimenti per entità che utilizzano il generatore IDENTITY. Per maggiori dettagli su questo argomento, consulta questo articolo .

Salva

Il savemetodo specifico di Hibernate precede JPA ed è stato disponibile sin dall'inizio del progetto Hibernate.

Il savemetodo attiva un SaveOrUpdateEventche è gestito dal DefaultSaveOrUpdateEventListenerlistener di eventi Hibernate. Pertanto, il savemetodo è equivalente ai metodi updatee saveOrUpdate.

Per vedere come funziona il savemetodo, considerare il seguente caso di test:

doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    Session session = entityManager.unwrap(Session.class);

    Long id = (Long) session.save(book);

    LOGGER.info(
        "Saving the Book entity with the id: {}", 
        id
    );
});

Quando si esegue il test case sopra, Hibernate genera le seguenti istruzioni SQL:

CALL NEXT VALUE FOR hibernate_sequence

-- Saving the Book entity with the id: 1

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

Come puoi vedere, il risultato è identico alla persistchiamata del metodo. Tuttavia, a differenza persistdel savemetodo, restituisce l'identificatore di entità.

Per maggiori dettagli, consulta questo articolo .

Aggiornare

Il updatemetodo specifico di Hibernate ha lo scopo di bypassare il meccanismo di controllo sporco e forzare l'aggiornamento di un'entità al momento del flush.

Il updatemetodo attiva un SaveOrUpdateEventche è gestito dal DefaultSaveOrUpdateEventListenerlistener di eventi Hibernate. Pertanto, il updatemetodo è equivalente ai metodi savee saveOrUpdate.

Per vedere come funziona il updatemetodo, prendere in considerazione il seguente esempio che persiste Bookun'entità in una transazione, quindi la modifica mentre l'entità è nello stato separato e forza SQL UPDATE usando la updatechiamata del metodo.

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    return book;
});

LOGGER.info("Modifying the Book entity");

_book.setTitle(
    "High-Performance Java Persistence, 2nd edition"
);

doInJPA(entityManager -> {
    Session session = entityManager.unwrap(Session.class);

    session.update(_book);

    LOGGER.info("Updating the Book entity");
});

Quando si esegue il test case sopra, Hibernate genera le seguenti istruzioni SQL:

CALL NEXT VALUE FOR hibernate_sequence

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

-- Modifying the Book entity
-- Updating the Book entity

UPDATE 
    book 
SET 
    author = 'Vlad Mihalcea', 
    isbn = '978-9730228236', 
    title = 'High-Performance Java Persistence, 2nd edition'
WHERE 
    id = 1

Si noti che UPDATEviene eseguito durante il flush del Persistence Context, subito prima del commit ed è per questo che il Updating the Book entitymessaggio viene registrato per primo.

Utilizzo @SelectBeforeUpdateper evitare aggiornamenti non necessari

Ora, l'AGGIORNAMENTO verrà sempre eseguito anche se l'entità non è stata modificata mentre si trova nello stato distaccato. Per evitare ciò, è possibile utilizzare l' @SelectBeforeUpdateannotazione Hibernate che attiverà SELECTun'istruzione recuperata loaded stateche verrà quindi utilizzata dal meccanismo di controllo sporco.

Quindi, se annotiamo l' Bookentità con l' @SelectBeforeUpdateannotazione:

@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {

    //Code omitted for brevity
}

Ed esegui il seguente test case:

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    return book;
});

doInJPA(entityManager -> {
    Session session = entityManager.unwrap(Session.class);

    session.update(_book);
});

Hibernate esegue le seguenti istruzioni SQL:

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

SELECT 
    b.id,
    b.author AS author2_0_,
    b.isbn AS isbn3_0_,
    b.title AS title4_0_
FROM 
    book b
WHERE 
    b.id = 1

Si noti che, questa volta, non viene UPDATEeseguito poiché il meccanismo di controllo sporco Hibernate ha rilevato che l'entità non è stata modificata.

SaveOrUpdate

Il saveOrUpdatemetodo specifico di Hibernate è solo un alias per savee update.

Il saveOrUpdatemetodo attiva un SaveOrUpdateEventche è gestito dal DefaultSaveOrUpdateEventListenerlistener di eventi Hibernate. Pertanto, il updatemetodo è equivalente ai metodi savee saveOrUpdate.

Ora puoi usare saveOrUpdatequando vuoi persistere un'entità o forzare una UPDATEcome illustrato dal seguente esempio.

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    Session session = entityManager.unwrap(Session.class);
    session.saveOrUpdate(book);

    return book;
});

_book.setTitle("High-Performance Java Persistence, 2nd edition");

doInJPA(entityManager -> {
    Session session = entityManager.unwrap(Session.class);
    session.saveOrUpdate(_book);
});

Attenzione al NonUniqueObjectException

Un problema che può verificarsi con save, updateed saveOrUpdateè se il contesto di persistenza contiene già un riferimento di entità con lo stesso ID e dello stesso tipo dell'esempio seguente:

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    Session session = entityManager.unwrap(Session.class);
    session.saveOrUpdate(book);

    return book;
});

_book.setTitle(
    "High-Performance Java Persistence, 2nd edition"
);

try {
    doInJPA(entityManager -> {
        Book book = entityManager.find(
            Book.class, 
            _book.getId()
        );

        Session session = entityManager.unwrap(Session.class);
        session.saveOrUpdate(_book);
    });
} catch (NonUniqueObjectException e) {
    LOGGER.error(
        "The Persistence Context cannot hold " +
        "two representations of the same entity", 
        e
    );
}

Ora, quando si esegue il test case sopra, Hibernate lancia un NonUniqueObjectException perché il secondo EntityManagercontiene già Bookun'entità con lo stesso identificativo di quello a cui passiamo updatee il contesto di persistenza non può contenere due rappresentazioni della stessa entità.

org.hibernate.NonUniqueObjectException: 
    A different object with the same identifier value was already associated with the session : [com.vladmihalcea.book.hpjp.hibernate.pc.Book#1]
    at org.hibernate.engine.internal.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:651)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:284)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:227)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:92)
    at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
    at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:682)
    at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:674)

Merge

Per evitare il NonUniqueObjectException , è necessario utilizzare il mergemetodo offerto dall'APP EntityManagere ereditato anche da Hibernate Session.

Come spiegato in questo articolo , mergerecupera una nuova istantanea dell'entità dal database se non è stato trovato alcun riferimento all'entità nel contesto di persistenza e copia lo stato dell'entità staccata passato al mergemetodo.

Il mergemetodo attiva un MergeEventche è gestito dal DefaultMergeEventListenerlistener di eventi Hibernate.

Per vedere come funziona il mergemetodo, considera il seguente esempio che persiste aBook un'entità in una transazione, quindi la modifica mentre l'entità è nello stato separato e passare l'entità staccata mergein un contesto di persistenza di sottosequenza.

Book _book = doInJPA(entityManager -> {
    Book book = new Book()
    .setIsbn("978-9730228236")
    .setTitle("High-Performance Java Persistence")
    .setAuthor("Vlad Mihalcea");

    entityManager.persist(book);

    return book;
});

LOGGER.info("Modifying the Book entity");

_book.setTitle(
    "High-Performance Java Persistence, 2nd edition"
);

doInJPA(entityManager -> {
    Book book = entityManager.merge(_book);

    LOGGER.info("Merging the Book entity");

    assertFalse(book == _book);
});

Durante l'esecuzione del test case sopra, Hibernate ha eseguito le seguenti istruzioni SQL:

INSERT INTO book (
    author, 
    isbn, 
    title, 
    id
) 
VALUES (
    'Vlad Mihalcea', 
    '978-9730228236', 
    'High-Performance Java Persistence', 
    1
)

-- Modifying the Book entity

SELECT 
    b.id,
    b.author AS author2_0_,
    b.isbn AS isbn3_0_,
    b.title AS title4_0_
FROM 
    book b
WHERE 
    b.id = 1

-- Merging the Book entity

UPDATE 
    book 
SET 
    author = 'Vlad Mihalcea', 
    isbn = '978-9730228236', 
    title = 'High-Performance Java Persistence, 2nd edition'
WHERE 
    id = 1

Si noti che il riferimento all'entità restituito da merge è diverso da quello staccato che abbiamo passato al mergemetodo.

Ora, anche se dovresti preferire l'utilizzo di JPA merge durante la copia dello stato di entità distaccata, il costo aggiuntivo SELECTpuò essere problematico durante l'esecuzione di un'attività di elaborazione batch.

Per questo motivo, dovresti preferire l'utilizzo updatequando sei sicuro che non ci siano riferimenti a entità già associati al contesto di persistenza attualmente in esecuzione e che l'entità staccata è stata modificata.

Per maggiori dettagli su questo argomento, consulta questo articolo .

Conclusione

Per mantenere un'entità, è necessario utilizzare il persistmetodo JPA . Per copiare lo stato di entità staccata, mergedovrebbe essere preferito. Il updatemetodo è utile solo per le attività di elaborazione batch. Gli savee saveOrUpdatesono solo degli aliasupdate e probabilmente non dovresti usarli affatto.

Alcuni sviluppatori chiamano save anche quando l'entità è già gestita, ma questo è un errore e attiva un evento ridondante poiché, per le entità gestite, l'AGGIORNAMENTO viene gestito automaticamente al momento dello scaricamento del contesto di persistenza.

Per maggiori dettagli, consulta questo articolo .


2

Tenere presente che se si chiama un aggiornamento su un oggetto disconnesso, nel database verrà sempre eseguito un aggiornamento, indipendentemente dal fatto che sia stato modificato l'oggetto. Se non è quello che vuoi, dovresti usare Session.lock () con LockMode.None.

È necessario chiamare l'aggiornamento solo se l'oggetto è stato modificato al di fuori dell'ambito della sessione corrente (in modalità disconnessa).



0

Nessuna delle risposte sopra è completa. Sebbene la risposta di Leo Theobald sembri la risposta più vicina.

Il punto di base è come l'ibernazione ha a che fare con gli stati delle entità e come li gestisce quando c'è un cambio di stato. Tutto deve essere visto per quanto riguarda le vampate e anche i commit, che tutti sembrano aver ignorato completamente.

NON UTILIZZARE MAI IL METODO SALVA DI HIBERNATE. DIMENTICATE CHE ESISTE ANCHE IN HIBERNATE!

Persistere

Come hanno spiegato tutti, Persist sostanzialmente passa un'entità dallo stato "Transitorio" allo Stato "Gestito". A questo punto, uno slush o un commit possono creare un'istruzione insert. Ma l'entità rimarrà comunque nello stato "Gestito". Ciò non cambia con il colore.

A questo punto, se "Persisti" di nuovo, non ci saranno cambiamenti. E non ci saranno altri salvataggi se proviamo a persistere un'entità persistente.

Il divertimento inizia quando proviamo a sfrattare l'entità.

Un sfratto è una funzione speciale di Hibernate che trasferirà l'entità da "Gestito" a "Staccato". Non possiamo chiamare persist su un'entità staccata. Se lo facciamo, Hibernate solleva un'eccezione e l'intera transazione viene ripristinata su commit.

Unisci vs Aggiorna

Queste sono 2 funzioni interessanti che fanno cose diverse se trattate in modi diversi. Entrambi stanno provando a spostare l'entità dallo stato "Staccato" allo stato "Gestito". Ma farlo diversamente.

Comprendi il fatto che Detached significa una specie di stato "offline". e gestito significa lo stato "Online".

Rispettare il codice seguente:

Session ses1 = sessionFactory.openSession();

    Transaction tx1 = ses1.beginTransaction();

    HibEntity entity = getHibEntity();

    ses1.persist(entity);
    ses1.evict(entity);

    ses1.merge(entity);

    ses1.delete(entity);

    tx1.commit();

Quando lo fai? Cosa pensi che succederà? Se hai detto che ciò genererà un'eccezione, allora hai ragione. Ciò genererà un'eccezione perché, la fusione ha funzionato sull'oggetto entità, che è stato distaccato. Ma non altera lo stato dell'oggetto.

Dietro la scena, l'unione genererà una query selezionata e sostanzialmente restituirà una copia dell'entità che si trova nello stato allegato. Rispettare il codice seguente:

Session ses1 = sessionFactory.openSession();

    Transaction tx1 = ses1.beginTransaction();
    HibEntity entity = getHibEntity();

    ses1.persist(entity);
    ses1.evict(entity);

    HibEntity copied = (HibEntity)ses1.merge(entity);
    ses1.delete(copied);

    tx1.commit();

L'esempio sopra funziona perché l'unione ha portato una nuova entità nel contesto che è nello stato persistente.

Se applicato con Update, lo stesso funziona correttamente perché in realtà l'aggiornamento non porta una copia dell'entità come unisci.

Session ses1 = sessionFactory.openSession();

    Transaction tx1 = ses1.beginTransaction();

    HibEntity entity = getHibEntity();

    ses1.persist(entity);
    ses1.evict(entity);

    ses1.update(entity);

    ses1.delete(entity);

    tx1.commit();

Allo stesso tempo, nella traccia di debug possiamo vedere che Update non ha generato query SQL di select like merge.

Elimina

Nell'esempio sopra ho usato delete senza parlare di delete. L'eliminazione sostanzialmente sposta l'entità dallo stato gestito allo stato "rimosso". E quando scaricato o commesso emetterà un comando di cancellazione da memorizzare.

Tuttavia è possibile riportare l'entità allo stato "gestito" dallo stato "rimosso" usando il metodo persist.

Spero che la spiegazione di cui sopra abbia chiarito eventuali dubbi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.