org.hibernate.PersistentObjectException: entidad separada pasada a persistir

89

Había escrito con éxito mi primer ejemplo de niño maestro con hibernate. Después de unos días lo volví a tomar y actualicé algunas bibliotecas. No estoy seguro de qué hice, pero nunca podría hacerlo funcionar de nuevo. ¿Alguien me ayudaría a descubrir qué está mal en el código que devuelve el siguiente mensaje de error?

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)

mapeo de hibernación:

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

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

EDITAR: objeto JSON enviado desde el cliente:

{"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: Algunos detalles:
he intentado guardar la factura siguiendo dos formas:

  1. Fabricado manualmente el objeto json mencionado anteriormente y lo pasó a una nueva sesión del servidor. En este caso, no se ha realizado absolutamente ninguna actividad antes de llamar al método save, por lo que no debería haber ninguna sesión abierta excepto la abierta en el método save

  2. Cargó los datos existentes mediante el método getInvoice y pasaron los mismos datos después de eliminar el valor de la clave. Esto también creo que debería cerrar la sesión antes de guardar, ya que la transacción se está comprometiendo en el método getInvoice.

En ambos casos, recibo el mismo mensaje de error que me obliga a creer que algo está mal, ya sea con el archivo de configuración de hibernación o las clases de entidad o el método de guardado.

Por favor, avíseme si debo proporcionar más detalles.

WSK
fuente

Respuestas:

119

No proporcionó muchos detalles relevantes, así que supongo que llamó getInvoicey luego usó el objeto de resultado para establecer algunos valores y llamar savecon la suposición de que los cambios de su objeto se guardarán.

Sin embargo, la persistoperación está destinada a objetos transitorios completamente nuevos y falla si la identificación ya está asignada. En su caso, probablemente desee llamar en saveOrUpdatelugar de persist.

Puede encontrar algunas discusiones y referencias aquí "entidad separada pasada para persistir error" con código JPA / EJB

Alex Gitelman
fuente
Gracias @Alex Gitelman. Agregué algunos detalles al final de mi pregunta original. ¿Ayuda a entender mi problema? o avíseme qué otros detalles serían útiles.
WSK
7
tu referencia me ayudó a encontrar un error estúpido. Estaba enviando un valor no nulo para "itemId" que es la clave principal en la tabla secundaria. Entonces, hibernate asumió que el objeto ya existe en alguna sesión. Gracias por el consejo
WSK
Ahora recibo este error: "org.hibernate.PropertyValueException: la propiedad no nula hace referencia a un valor nulo o transitorio: example.forms.InvoiceItem.invoice". ¿Podría darme alguna pista? Gracias de antemano
WSK
Debe tener la factura en estado persistente, no transitorio. Eso significa que la identificación ya debe estar asignada. Así que guarde Invoiceprimero, para que obtenga id y luego guarde InvoiceItem. También puedes jugar con cascada.
Alex Gitelman
13

Aquí ha utilizado nativo y asignando valor a la clave primaria, en la clave primaria nativa se genera automáticamente.

De ahí viene el tema.

Bibhav
fuente
1
Si cree que tiene información adicional que ofrecer sobre una pregunta que ya tiene una respuesta aceptada, proporcione una explicación más sustancial.
ChicagoRedSox
8

Esto existe en la relación @ManyToOne. Resolví este problema simplemente usando CascadeType.MERGE en lugar de CascadeType.PERSIST o CascadeType.ALL. Espero que te ayude.

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

Solución:

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

Lo más probable es que el problema esté fuera del código que nos muestra aquí. Está intentando actualizar un objeto que no está asociado con la sesión actual. Si no es el Invoice, entonces tal vez sea un InvoiceItem que ya ha sido persistido, obtenido de la base de datos, mantenido vivo en algún tipo de sesión y luego intentas persistirlo en una nueva sesión. Esto no es posible. Como regla general, nunca mantenga vivos sus objetos persistentes durante las sesiones.

La solución consistirá, por ejemplo, en obtener el gráfico de objetos completo de la misma sesión con la que está intentando persistir. En un entorno web, esto significaría:

  • Obtener la sesión
  • Obtenga los objetos que necesita actualizar o agregar asociaciones. Preferiblemente por su clave principal
  • Altere lo que se necesita
  • Guardar / actualizar / desalojar / eliminar lo que desee
  • Cerrar / confirmar su sesión / transacción

Si sigue teniendo problemas, publique parte del código que llama a su servicio.

Joostschouten
fuente
Gracias @joostschouten. Aparentemente, no debería haber una sesión abierta antes de llamar al método de guardar como mencioné en "Más detalles" que agregué al final de mi pregunta original. ¿Hay alguna forma de que pueda verificar si existe alguna sesión antes de llamar al método save?
WSK
Su suposición "Aparentemente no debería haber una sesión abierta antes de llamar al método save" es incorrecta. En su caso, está envolviendo una transacción alrededor de cada guardado y obtención, lo que significa que las sesiones abiertas no deberían ocurrir y si lo harán no serán de utilidad. Su problema parece estar en el código que maneja su JSON. Aquí pasa una factura con los elementos de la factura que ya existen (tienen identificaciones). Páselo con ID nulos y lo más probable es que funcione. O haga que su servicio que maneja el JSON obtenga los elementos de la factura de la base de datos, agréguelos a la factura y guárdelos en la misma sesión de la que los obtuvo.
joostschouten
@joostschouten Ahora recibo este error: "org.hibernate.PropertyValueException: la propiedad no nula hace referencia a un valor nulo o transitorio: example.forms.InvoiceItem.invoice". ¿Podría darme alguna idea? Gracias de antemano
WSK
1
Esto me suena como una pregunta nueva. No ha compartido con nosotros un código importante. El código que se ocupa del JSON genera los objetos de su modelo y las llamadas persisten y se guardan. Esta excepción le indica que está intentando conservar un invoiceItem con una factura nula. Lo que legítimamente no se puede hacer. Publique el código que realmente crea los objetos de su modelo.
joostschouten
@joostschouten Esto tiene sentido para mí, pero el problema es que estoy usando un marco "qooxdoo" para JSON y creando una llamada RPC al servidor donde tengo una utilidad de servidor RPC del mismo marco instalado. Entonces todo está envuelto en clases marco. Puede que no sea práctico extraer y publicar miles de líneas. Por otro lado, podemos ver el objeto "theInvoice" en el lado del servidor que se ha creado. o mostrando información de depuración / seguimiento de hibernación?
WSK
2

Dos soluciones 1. use merge si desea actualizar el objeto 2. use save si solo desea guardar un nuevo objeto (asegúrese de que la identidad sea nula para permitir que la hibernación o la base de datos la generen) 3. si está usando mapeo como
@OneToOne ( fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinColumn (nombre = "stock_id")

Luego use CascadeType.ALL para CascadeType.MERGE

gracias Shahid Abbasi

Shahid Hussain Abbasi
fuente
0

Para JPA corregido usando EntityManager merge () en lugar de persist ()

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

Tuve el "mismo" problema porque estaba escribiendo

@GeneratedValue(strategy = GenerationType.IDENTITY)

Eliminé esa línea porque no la necesito en este momento, estaba probando con objetos y así. Creo que es <generator class="native" />en tu caso

No tengo ningún controlador y no se está accediendo a mi API, es solo para probar (por el momento).

Miguel Ávila
fuente