org.hibernate.PersistentObjectException: entità distaccata passata a persistere


90

Avevo scritto con successo il mio primo esempio di master child con ibernazione. Dopo pochi giorni l'ho ripreso e ho aggiornato alcune librerie. Non sono sicuro di cosa ho fatto ma non potrei mai farlo funzionare di nuovo. Qualcuno potrebbe aiutarmi a capire cosa c'è di sbagliato nel codice che restituisce il seguente messaggio di errore:

org.hibernate.PersistentObjectException: detached entity passed to persist: example.forms.InvoiceItem
    at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:127)
    at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:799)
    at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:791)
    .... (truncated)

ibernazione mappatura:

<hibernate-mapping package="example.forms">
    <class name="Invoice" table="Invoices">
        <id name="id" type="long">
            <generator class="native" />
        </id>
        <property name="invDate" type="timestamp" />
        <property name="customerId" type="int" />
        <set cascade="all" inverse="true" lazy="true" name="items" order-by="id">
            <key column="invoiceId" />
            <one-to-many class="InvoiceItem" />
        </set>
    </class>
    <class name="InvoiceItem" table="InvoiceItems">
        <id column="id" name="itemId" type="long">
            <generator class="native" />
        </id>
        <property name="productId" type="long" />
        <property name="packname" type="string" />
        <property name="quantity" type="int" />
        <property name="price" type="double" />
        <many-to-one class="example.forms.Invoice" column="invoiceId" name="invoice" not-null="true" />
    </class>
</hibernate-mapping>

EDIT: InvoiceManager.java

class InvoiceManager {

    public Long save(Invoice theInvoice) throws RemoteException {
        Session session = HbmUtils.getSessionFactory().getCurrentSession();
        Transaction tx = null;
        Long id = null;
        try {
            tx = session.beginTransaction();
            session.persist(theInvoice);
            tx.commit();
            id = theInvoice.getId();
        } catch (RuntimeException e) {
            if (tx != null)
                tx.rollback();
            e.printStackTrace();
            throw new RemoteException("Invoice could not be saved");
        } finally {
            if (session.isOpen())
                session.close();
        }
        return id;
    }

    public Invoice getInvoice(Long cid) throws RemoteException {
        Session session = HbmUtils.getSessionFactory().getCurrentSession();
        Transaction tx = null;
        Invoice theInvoice = null;
        try {
            tx = session.beginTransaction();
            Query q = session
                    .createQuery(
                            "from Invoice as invoice " +
                            "left join fetch invoice.items as invoiceItems " +
                            "where invoice.id = :id ")
                    .setReadOnly(true);
            q.setParameter("id", cid);
            theInvoice = (Invoice) q.uniqueResult();
            tx.commit();
        } catch (RuntimeException e) {
            tx.rollback();
        } finally {
            if (session.isOpen())
                session.close();
        }
        return theInvoice;
    }
}

Invoice.java

public class Invoice implements java.io.Serializable {

    private Long id;
    private Date invDate;
    private int customerId;
    private Set<InvoiceItem> items;

    public Long getId() {
        return id;
    }

    public Date getInvDate() {
        return invDate;
    }

    public int getCustomerId() {
        return customerId;
    }

    public Set<InvoiceItem> getItems() {
        return items;
    }

    void setId(Long id) {
        this.id = id;
    }

    void setInvDate(Date invDate) {
        this.invDate = invDate;
    }

    void setCustomerId(int customerId) {
        this.customerId = customerId;
    }

    void setItems(Set<InvoiceItem> items) {
        this.items = items;
    }
}

InvoiceItem.java

public class InvoiceItem implements java.io.Serializable {

    private Long itemId;
    private long productId;
    private String packname;
    private int quantity;
    private double price;
    private Invoice invoice;

    public Long getItemId() {
        return itemId;
    }

    public long getProductId() {
        return productId;
    }

    public String getPackname() {
        return packname;
    }

    public int getQuantity() {
        return quantity;
    }

    public double getPrice() {
        return price;
    }

    public Invoice getInvoice() {
        return invoice;
    }

    void setItemId(Long itemId) {
        this.itemId = itemId;
    }

    void setProductId(long productId) {
        this.productId = productId;
    }

    void setPackname(String packname) {
        this.packname = packname;
    }

    void setQuantity(int quantity) {
        this.quantity = quantity;
    }

    void setPrice(double price) {
        this.price = price;
    }

    void setInvoice(Invoice invoice) {
        this.invoice = invoice;
    }
}

EDIT: oggetto JSON inviato dal client:

{"id":null,"customerId":3,"invDate":"2005-06-07T04:00:00.000Z","items":[
{"itemId":1,"productId":1,"quantity":10,"price":100},
{"itemId":2,"productId":2,"quantity":20,"price":200},
{"itemId":3,"productId":3,"quantity":30,"price":300}]}

EDIT: alcuni dettagli:
ho provato a salvare la fattura seguendo due modi:

  1. Oggetto json sopra menzionato fabbricato manualmente e passato alla nuova sessione del server. In questo caso non è stata fatta assolutamente nessuna attività prima di chiamare il metodo di salvataggio quindi non dovrebbe esserci alcuna sessione aperta tranne quella aperta nel metodo di salvataggio

  2. Sono stati caricati i dati esistenti utilizzando il metodo getInvoice e hanno passato gli stessi dati dopo aver rimosso il valore della chiave. Anche questo credo dovrebbe chiudere la sessione prima di salvare poiché la transazione viene impegnata nel metodo getInvoice.

In entrambi i casi ricevo lo stesso messaggio di errore che mi costringe a credere che qualcosa non va nel file di configurazione di ibernazione o nelle classi di entità o nel metodo di salvataggio.

Per favore fatemi sapere se devo fornire maggiori dettagli

Risposte:


120

Non hai fornito molti dettagli rilevanti, quindi immagino che tu abbia chiamato getInvoicee quindi hai usato l'oggetto risultato per impostare alcuni valori e chiamare savesupponendo che le modifiche all'oggetto verranno salvate.

Tuttavia, l' persistoperazione è intesa per nuovi oggetti temporanei e non riesce se l'id è già assegnato. Nel tuo caso probabilmente vorrai chiamare saveOrUpdateinvece di persist.

Puoi trovare alcune discussioni e riferimenti qui "entità scollegata passata a persistere errore" con codice JPA / EJB


Grazie @Alex Gitelman. Ho aggiunto alcuni dettagli in fondo alla mia domanda originale. Aiuta a capire il mio problema? oppure fammi sapere quali altri dettagli sarebbero utili.
WSK

7
il tuo riferimento mi ha aiutato a trovare uno stupido errore. Stavo inviando un valore non nullo per "itemId" che è la chiave primaria nella tabella figlio. Quindi l'ibernazione presupponeva che l'oggetto esistesse già in qualche sessione. Grazie per il consiglio
WSK

Ora ricevo questo errore: "org.hibernate.PropertyValueException: la proprietà not-null fa riferimento a un valore null o temporaneo: example.forms.InvoiceItem.invoice". Potresti darmi qualche suggerimento? Grazie in anticipo
WSK

Devi avere Fattura in stato persistente, non transitorio. Ciò significa che l'id deve essere già assegnato ad esso. Quindi salva Invoiceprima, così otterrà l'ID e poi salverà InvoiceItem. Puoi anche giocare con la cascata.
Alex Gitelman

14

Qui hai usato native e assegnando un valore alla chiave primaria, nella chiave primaria nativa viene generata automaticamente.

Quindi il problema sta arrivando.


1
Se ritieni di avere ulteriori informazioni da offrire per una domanda che ha già una risposta accettata, fornisci una spiegazione più sostanziale.
ChicagoRedSox

11

Questo esiste nella relazione @ManyToOne. Ho risolto questo problema semplicemente utilizzando CascadeType.MERGE invece di CascadeType.PERSIST o CascadeType.ALL. Spero ti aiuti.

@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name="updated_by", referencedColumnName = "id")
private Admin admin;

Soluzione:

@ManyToOne(cascade = CascadeType.MERGE)
@JoinColumn(name="updated_by", referencedColumnName = "id")
private Admin admin;

4

Molto probabilmente il problema risiede al di fuori del codice che ci stai mostrando qui. Stai tentando di aggiornare un oggetto che non è associato alla sessione corrente. Se non è la fattura, allora forse è un InvoiceItem che è già stato reso persistente, ottenuto dal db, tenuto in vita in una sorta di sessione e quindi si tenta di persisterlo in una nuova sessione. Non è possibile. Come regola generale, non mantenere mai vivi gli oggetti persistenti durante le sessioni.

La soluzione sarà cioè ottenere l'intero oggetto grafico dalla stessa sessione con cui stai cercando di persistere. In un ambiente web questo significherebbe:

  • Ottieni la sessione
  • Recupera gli oggetti che devi aggiornare o aggiungi associazioni. Preferibilmente dalla loro chiave primaria
  • Modifica ciò che è necessario
  • Salva / aggiorna / elimina / elimina ciò che desideri
  • Chiudi / salva la tua sessione / transazione

Se continui ad avere problemi, pubblica parte del codice che sta chiamando il tuo servizio.


Grazie @joostschouten. Apparentemente non dovrebbe esserci una sessione aperta prima di chiamare il metodo di salvataggio come ho menzionato in "Ulteriori dettagli" che ho aggiunto in fondo alla mia domanda originale. C'è un modo per controllare se esiste una sessione prima di chiamare il metodo di salvataggio?
WSK

La tua supposizione "Apparentemente non dovrebbe esserci una sessione aperta prima di chiamare il metodo di salvataggio" è sbagliata. Nel tuo caso stai avvolgendo una transazione attorno a ogni save and get, il che significa che le sessioni aperte non dovrebbero verificarsi e se non saranno utili. Il tuo problema sembra risiedere nel codice che gestisce il tuo JSON. Qui si passa una fattura con elementi di fattura già esistenti (hanno gli ID). Passalo con ID nulli e molto probabilmente funzionerà. Oppure chiedi al tuo servizio di gestire il JSON di ottenere gli elementi della fattura dal database, aggiungerli alla fattura e salvarli nella stessa sessione da cui li hai ottenuti.
joostschouten

@joostschouten Ora ricevo questo errore: "org.hibernate.PropertyValueException: la proprietà not-null fa riferimento a un valore nullo o transitorio: example.forms.InvoiceItem.invoice". Potresti darmi un'idea? Grazie in anticipo
WSK

1
Questa suona come una nuova domanda per me. Non hai condiviso con noi un'importante parte di codice. Il codice che si occupa del JSON, genera il tuo modello Oggetti e le chiamate persistono e salvano. Questa eccezione indica che stai tentando di rendere persistente un invoiceItem con una fattura nulla. Che giustamente non si può fare. Pubblica il codice che costruisce effettivamente gli oggetti del tuo modello.
joostschouten

@joostschouten Questo ha senso per me, ma il problema è che sto usando un framework "qooxdoo" per JSON e sto creando una chiamata RPC al server in cui ho un'utilità del server RPC dallo stesso framework installato. Quindi tutto è racchiuso in classi framework. Potrebbe non essere pratico estrarre e pubblicare migliaia di righe. D'altra parte possiamo guardare l'oggetto "theInvoice" nel lato server che è stato creato? o mostrando le informazioni di debug / traccia di ibernazione?
WSK

2

Due soluzioni 1. usa merge se vuoi aggiornare l'oggetto 2. usa save se vuoi solo salvare un nuovo oggetto (assicurati che l'identità sia nulla per lasciarlo ibernare o generare il database) 3. se stai usando una mappatura come
@OneToOne ( fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinColumn (name = "stock_id")

Quindi utilizzare CascadeType.ALL per CascadeType.MERGE

grazie Shahid Abbasi


1

Ho avuto lo "stesso" problema perché stavo scrivendo

@GeneratedValue(strategy = GenerationType.IDENTITY)

Ho cancellato quella riga perché al momento non mi serve, stavo testando con oggetti e così via. Penso che sia <generator class="native" />nel tuo caso

Non ho alcun controller e non si accede alla mia API, è solo per il test (al momento).


0

Per JPA risolto utilizzando EntityManager merge () invece di persist ()

EntityManager em = getEntityManager();
    try {
        em.getTransaction().begin();
        em.merge(fieldValue);
        em.getTransaction().commit();
    } catch (Exception e) {
        //do smthng
    } finally {
        em.close();
    }
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.