106 votes

org.hibernate.PersistentObjectException : entité détachée passée à la persistance

J'avais réussi à écrire mon premier exemple de maître enfant avec hibernate. Après quelques jours, je l'ai repris et mis à jour certaines bibliothèques. Je ne suis pas sûr de ce que j'ai fait mais je n'ai jamais pu le faire fonctionner à nouveau. Quelqu'un pourrait-il m'aider à comprendre ce qui ne va pas dans le code qui renvoie le message d'erreur suivant ?

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)

cartographie hibernate :

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

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

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

EDITAR: Objet JSON envoyé par le 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}]}

EDITAR: Quelques détails :
J'ai essayé d'économiser la facture en suivant deux méthodes :

  1. Fabrication manuelle de l'objet susmentionné et l'a passé à une nouvelle session du serveur. Dans ce cas, absolument aucune activité n'a été effectuée avant d'appeler la méthode save, donc il ne devrait pas y avoir de session ouverte sauf celle ouverte par la méthode de sauvegarde

  2. Chargement des données existantes en utilisant méthode getInvoice et leur a transmis les mêmes données après avoir supprimé la valeur de la clé. Je pense que cela aussi devrait fermer la session avant de la sauvegarder car car la transaction est engagée dans la méthode getInvoice.

Dans les deux cas, j'obtiens le même message d'erreur qui me pousse à croire que quelque chose ne va pas, soit dans le fichier de configuration d'Hibernate, soit dans les classes d'entités, soit dans la méthode de sauvegarde.

Veuillez me faire savoir si je dois fournir plus de détails.

135voto

Alex Gitelman Points 15062

Vous n'avez pas fourni beaucoup de détails pertinents, donc je suppose que vous avez appelé getInvoice et ensuite vous avez utilisé l'objet résultat pour définir certaines valeurs et appeler save en supposant que vos modifications d'objet seront sauvegardées.

Cependant, persist L'opération est destinée aux nouveaux objets transitoires et échoue si l'identifiant est déjà attribué. Dans votre cas, vous voulez probablement appeler saveOrUpdate au lieu de persist .

Vous pouvez trouver quelques discussions et références ici erreur "detached entity passed to persist" avec le code JPA/EJB

0 votes

Merci @Alex Gitelman. J'ai ajouté quelques détails au bas de ma question initiale. Est-ce que cela aide à comprendre mon problème ? ou bien faites-moi savoir quels autres détails seraient utiles.

11 votes

Votre référence m'a aidé à trouver une erreur stupide. J'ai envoyé une valeur non nulle pour "itemId" qui est la clé primaire de la table enfant. Ainsi, Hibernate a supposé que l'objet existait déjà dans une session. Merci pour vos conseils

0 votes

Maintenant, je reçois cette erreur : "org.hibernate.PropertyValueException : not-null property references a null or transient value : example.forms.InvoiceItem.invoice". Pouvez-vous me donner un indice ? Merci d'avance

28voto

Kavitha yadav Points 267

Ceci existe dans la relation @ManyToOne. J'ai résolu ce problème en utilisant simplement CascadeType.MERGE au lieu de CascadeType.PERSIST ou CascadeType.ALL. J'espère que cela vous aidera.

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

Solution :

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

16voto

Bibhav Points 21

Ici, vous avez utilisé la méthode native et vous avez assigné une valeur à la clé primaire, dans la méthode native la clé primaire est générée automatiquement.

D'où la question qui se pose.

1 votes

Si vous pensez avoir des informations supplémentaires à fournir pour une question qui a déjà une réponse acceptée, veuillez fournir une explication plus substantielle.

7voto

jafarmlp Points 999

Je partage mon expérience car le titre de la question est plus générique. L'une des raisons pourrait être que l'@GeneratedValue est utilisée pour un identifiant unique, mais que l'identifiant est ensuite redéfini.

Exemple

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "COURSE")
public class Course {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

}

Le code suivant lève l'exception : org.hibernate.PersistentObjectException: detached entity passed to persist:a.b.c.Course parce que L'identité est définie dans le constructeur.

Course course = new Course(10L, "testcourse");
testEntityManager.persist(course);

Solution

Évitez de définir l'identifiant, car il est généré automatiquement (comme spécifié dans la classe de l'entité).

Course course = new Course();
course.setName("testcourse");
testEntityManager.persist(course);

4voto

joostschouten Points 2438

Il est fort probable que le problème se situe en dehors du code que vous nous montrez ici. Vous essayez de mettre à jour un objet qui n'est pas associé à la session actuelle. Si ce n'est pas la facture, il s'agit peut-être d'un InvoiceItem qui a déjà été persisté, obtenu de la base de données, maintenu en vie dans une sorte de session, puis vous essayez de le persister dans une nouvelle session. Cela n'est pas possible. En règle générale, ne gardez jamais vos objets persistés en vie à travers les sessions.

La solution consistera à obtenir le graphe d'objets complet à partir de la même session que celle avec laquelle vous essayez de le faire persister. Dans un environnement web, cela signifie :

  • Obtenir la session
  • Récupérez les objets que vous devez mettre à jour ou auxquels vous devez ajouter des associations. De préférence par leur clé primaire
  • Modifier ce qui est nécessaire
  • Sauvegarder/mettre à jour/évoluer/supprimer ce que vous voulez
  • Fermer/commander votre session/transaction

Si vous continuez à avoir des problèmes, postez une partie du code qui appelle votre service.

0 votes

Merci @joostschouten. Apparemment, il ne devrait pas y avoir de session ouverte avant l'appel de la méthode de sauvegarde, comme je l'ai mentionné dans la section "Plus de détails" que j'ai ajoutée au bas de ma question initiale. Existe-t-il un moyen de vérifier si une session existe avant d'appeler la méthode de sauvegarde ?

0 votes

Votre hypothèse "Apparemment, il ne devrait pas y avoir de session ouverte avant d'appeler la méthode de sauvegarde" est fausse. Dans votre cas, vous enveloppez une transaction autour de chaque sauvegarde et récupération, ce qui signifie que les sessions ouvertes ne devraient pas se produire et, si elles le font, elles ne sont d'aucune utilité. Votre problème semble se situer dans le code qui traite votre JSON. Ici, vous transmettez une facture avec des éléments de facture qui existent déjà (ils ont des identifiants). Passez-la avec des identifiants nuls et cela fonctionnera très probablement. Ou bien, faites en sorte que votre service qui gère le JSON obtienne les éléments de la facture à partir de la base de données, les ajoute à la facture et les enregistre dans la même session que celle où vous les avez obtenus.

0 votes

@joostschouten J'obtiens maintenant cette erreur : "org.hibernate.PropertyValueException : not-null property references a null or transient value : example.forms.InvoiceItem.invoice". Pouvez-vous me donner une idée ? Merci d'avance

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X