org.hibernate.PersistentObjectException: Abgetrennte Entität, die an persist übergeben wurde

87

Ich hatte mein erstes Masterkind-Beispiel mit Ruhezustand erfolgreich geschrieben. Nach ein paar Tagen nahm ich es wieder und aktualisierte einige Bibliotheken. Ich bin mir nicht sicher, was ich getan habe, aber ich konnte es nie wieder zum Laufen bringen. Würde mir jemand helfen, herauszufinden, was im Code falsch ist, der die folgende Fehlermeldung zurückgibt:

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)

Zuordnung im Ruhezustand:

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

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

BEARBEITEN: Vom Client gesendetes JSON-Objekt:

{"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}]}

BEARBEITEN: Einige Details:
Ich habe versucht, die Rechnung auf zwei Arten zu speichern:

  1. Das oben erwähnte JSON-Objekt wurde manuell hergestellt und an eine neue Serversitzung übergeben. In diesem Fall wurde vor dem Aufrufen der Speichermethode absolut keine Aktivität ausgeführt, daher sollte keine offene Sitzung außer der in der Speichermethode geöffneten Sitzung vorhanden sein

  2. Laden vorhandener Daten mithilfe der Methode getInvoice und übergeben dieselben Daten nach dem Entfernen des Schlüsselwerts. Auch dies sollte meiner Meinung nach die Sitzung vor dem Speichern schließen, da die Transaktion in der getInvoice-Methode festgeschrieben wird.

In beiden Fällen wird dieselbe Fehlermeldung angezeigt, die mich zu der Annahme zwingt, dass entweder mit der Konfigurationsdatei im Ruhezustand oder mit Entitätsklassen oder der Speichermethode etwas nicht stimmt.

Bitte lassen Sie mich wissen, ob ich weitere Details angeben soll

WSK
quelle

Antworten:

117

Sie haben nicht viele relevante Details angegeben, daher werden Sie vermutlich aufgerufen getInvoiceund dann das Ergebnisobjekt verwendet, um einige Werte festzulegen und savemit der Annahme aufzurufen, dass Ihre Objektänderungen gespeichert werden.

Die persistOperation ist jedoch für brandneue transiente Objekte vorgesehen und schlägt fehl, wenn die ID bereits zugewiesen ist. In Ihrem Fall möchten Sie wahrscheinlich saveOrUpdatestatt anrufen persist.

Hier finden Sie einige Diskussionen und Verweise auf "getrennte Entität, die an einen dauerhaften Fehler übergeben wurde" mit JPA / EJB-Code

Alex Gitelman
quelle
Vielen Dank an Alex Gitelman. Ich habe am Ende meiner ursprünglichen Frage einige Details hinzugefügt. Hilft es, mein Problem zu verstehen? oder lassen Sie mich wissen, welche anderen Details hilfreich wären.
WSK
7
Ihre Referenz hat mir geholfen, einen dummen Fehler zu finden. Ich habe keinen Nullwert für "itemId" gesendet, der der Primärschlüssel in der untergeordneten Tabelle ist. Der Ruhezustand ging also davon aus, dass das Objekt in einer Sitzung bereits vorhanden ist. Vielen Dank für den Rat
WSK
Jetzt wird folgende Fehlermeldung angezeigt: "org.hibernate.PropertyValueException: Die Eigenschaft not-null verweist auf einen Null- oder Übergangswert: example.forms.InvoiceItem.invoice". Könnten Sie mir bitte einen Hinweis geben? Vielen Dank im Voraus
WSK
Die Rechnung muss dauerhaft und nicht vorübergehend sein. Das bedeutet, dass ihm bereits eine ID zugewiesen werden muss. Speichern Sie also Invoicezuerst, damit die ID angezeigt wird, und speichern Sie sie dann InvoiceItem. Sie können auch mit Kaskadierung spielen.
Alex Gitelman
13

Hier haben Sie den nativen Primärschlüssel verwendet und dem Primärschlüssel einen Wert zugewiesen. Der native Primärschlüssel wird automatisch generiert.

Daher kommt das Problem.

Bibhav
quelle
1
Wenn Sie der Meinung sind, dass Sie zusätzliche Informationen für eine Frage anbieten können, für die bereits eine Antwort akzeptiert wurde, geben Sie bitte eine ausführlichere Erklärung.
ChicagoRedSox
5

Dies ist in der Beziehung @ManyToOne vorhanden. Ich habe dieses Problem gelöst, indem ich nur CascadeType.MERGE anstelle von CascadeType.PERSIST oder CascadeType.ALL verwendet habe. Hoffe es hilft dir.

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

Lösung:

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

Höchstwahrscheinlich liegt das Problem außerhalb des Codes, den Sie uns hier zeigen. Sie versuchen, ein Objekt zu aktualisieren, das nicht der aktuellen Sitzung zugeordnet ist. Wenn es sich nicht um die Rechnung handelt, handelt es sich möglicherweise um ein InvoiceItem, das bereits beibehalten wurde, von der Datenbank abgerufen wurde, in einer Sitzung am Leben gehalten wurde, und dann versuchen Sie, es in einer neuen Sitzung beizubehalten. Das ist nicht möglich. Halten Sie Ihre persistierten Objekte in der Regel niemals über Sitzungen hinweg am Leben.

Die Lösung besteht darin, das gesamte Objektdiagramm aus derselben Sitzung zu erhalten, in der Sie versuchen, es beizubehalten. In einer Webumgebung würde dies bedeuten:

  • Erhalten Sie die Sitzung
  • Rufen Sie die Objekte ab, die Sie aktualisieren oder zu denen Sie Assoziationen hinzufügen möchten. Vorzugsweise nach ihrem Primärschlüssel
  • Ändern Sie, was benötigt wird
  • Speichern / aktualisieren / räumen / löschen Sie, was Sie wollen
  • Schließen / Festschreiben Ihrer Sitzung / Transaktion

Wenn Sie weiterhin Probleme haben, veröffentlichen Sie einen Teil des Codes, der Ihren Dienst aufruft.

Joostschouten
quelle
Danke @joostschouten. Anscheinend sollte es keine offene Sitzung geben, bevor die Speichermethode aufgerufen wird, wie ich unter "Weitere Details" erwähnt habe, die ich unten meiner ursprünglichen Frage hinzugefügt habe. Gibt es eine Möglichkeit, zu überprüfen, ob eine Sitzung vorhanden ist, bevor ich die Speichermethode aufrufe?
WSK
Ihre Annahme "Anscheinend sollte vor dem Aufruf der Speichermethode keine Sitzung geöffnet sein" ist falsch. In Ihrem Fall wickeln Sie eine Transaktion um jedes Speichern und Abrufen, was bedeutet, dass offene Sitzungen nicht stattfinden sollten und wenn sie keinen Nutzen haben. Ihr Problem scheint in dem Code zu liegen, der Ihren JSON behandelt. Hier übergeben Sie eine Rechnung mit bereits vorhandenen Rechnungsposten (mit IDs). Übergeben Sie es mit Null-IDs und es wird höchstwahrscheinlich funktionieren. Oder lassen Sie Ihren Service, der den JSON verwaltet, die Rechnungen von der Datenbank abrufen, zur Rechnung hinzufügen und in derselben Sitzung speichern, von der Sie sie erhalten haben.
Joostschouten
@joostschouten Jetzt wird folgende Fehlermeldung angezeigt: "org.hibernate.PropertyValueException: Die Eigenschaft not-null verweist auf einen Null- oder Übergangswert: example.forms.InvoiceItem.invoice". Könnten Sie mir bitte eine Idee geben? Vielen Dank im Voraus
WSK
1
Das klingt für mich nach einer neuen Frage. Sie haben uns keinen wichtigen Code mitgeteilt. Der Code, der sich mit JSON befasst, generiert Ihre Modellobjekte und Aufrufe bleiben bestehen und werden gespeichert. Diese Ausnahme gibt an, dass Sie versuchen, ein rechnungselement mit einer Nullrechnung beizubehalten. Was zu Recht nicht möglich ist. Bitte posten Sie den Code, der Ihre Modellobjekte tatsächlich erstellt.
Joostschouten
@joostschouten Dies ist für mich sinnvoll, aber das Problem ist, dass ich ein Framework "qooxdoo" für JSON verwende und einen RPC-Aufruf an den Server erstelle, auf dem ein RPC-Server-Dienstprogramm aus demselben Framework installiert ist. Also ist alles in Framework-Klassen verpackt. Es ist möglicherweise nicht praktisch, Tausende von Zeilen zu extrahieren und zu veröffentlichen. Auf der anderen Seite können wir das Objekt "theInvoice" auf der Serverseite beobachten, das erstellt wurde? oder durch Anzeigen von Debug- / Trace-Informationen im Ruhezustand?
WSK
2

Zwei Lösungen: 1. Verwenden Sie Merge, wenn Sie das Objekt aktualisieren möchten. 2. Verwenden Sie save, wenn Sie nur ein neues Objekt speichern möchten (stellen Sie sicher, dass die Identität null ist, damit der Ruhezustand oder die Datenbank es generieren können.) 3. Wenn Sie eine Zuordnung wie
@OneToOne verwenden. fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinColumn (name = "stock_id")

Verwenden Sie dann CascadeType.ALL für CascadeType.MERGE

danke Shahid Abbasi

Shahid Hussain Abbasi
quelle
0

Für JPA, das mit EntityManager behoben wurde, wird merge () anstelle von persist () verwendet.

EntityManager em = getEntityManager();
    try {
        em.getTransaction().begin();
        em.merge(fieldValue);
        em.getTransaction().commit();
    } catch (Exception e) {
        //do smthng
    } finally {
        em.close();
    }
JeSa
quelle
0

Ich hatte das "gleiche" Problem, weil ich schrieb

@GeneratedValue(strategy = GenerationType.IDENTITY)

Ich habe diese Zeile gelöscht, weil ich sie im Moment nicht brauche. Ich habe mit Objekten getestet und so. Ich denke es ist <generator class="native" />in deinem Fall

Ich habe keinen Controller und auf meine API wird nicht zugegriffen, sondern nur zum Testen (im Moment).

Miguel Avila
quelle