125

I had successfully written my first master child example with hibernate. After a few days I took it again and upgraded some of the libraries. Not sure what did I do but I could never make it run again. Would somebody help my figure out what is wrong in code that is returning following error message:

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)

hibernate mapping:

<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: JSON object sent from 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: Some details:
I have tried to save invoice by following two ways:

  1. Manually fabricated above mentioned json object and passed it to fresh session of server. In this case absolutely no activity has been made prior to calling save method so there should not be any open session except the one opened in save method

  2. Loaded existing data by using getInvoice method and them passed same data after removing key value. This too I believe should close the session before saving as transaction is being committed in getInvoice method.

In both cases I am getting same error message that is forcing me to believe that something is wrong either with hibernate configuration file or entity classes or save method.

Please let me know if I should provide more details

WSK
  • 5,949
  • 8
  • 49
  • 74

10 Answers10

155

You didn't provide many relevant details so I will guess that you called getInvoice and then you used result object to set some values and call save with assumption that your object changes will be saved.

However, persist operation is intended for brand new transient objects and it fails if id is already assigned. In your case you probably want to call saveOrUpdate instead of persist.

You can find some discussion and references here "detached entity passed to persist error" with JPA/EJB code

Community
  • 1
  • 1
Alex Gitelman
  • 24,429
  • 7
  • 52
  • 49
  • Thank you @Alex Gitelman. I have added some details at the bottom of my original question. Does it help to understand my problem? or please let me know what other details would be helpful. – WSK Jun 17 '11 at 13:45
  • 12
    your reference helped me find stupid mistake. I was sending not null value for "itemId" that is primary key in child table. So hibernate was making assumption that object already exists in some session. Thank you for the advice – WSK Jun 17 '11 at 14:07
  • Now I am getting this error:"org.hibernate.PropertyValueException: not-null property references a null or transient value: example.forms.InvoiceItem.invoice". Could you please give me some hint? Thanks in advance – WSK Jun 17 '11 at 16:09
  • You have to have Invoice in persisted state, not transient. That means that id must be assigned to it already. So save `Invoice` first, so it will get id and then save `InvoiceItem`. You may also play with cascading. – Alex Gitelman Jun 17 '11 at 17:34
42

This exists in @ManyToOne relation. I solved this issue by just using CascadeType.MERGE instead of CascadeType.PERSIST or CascadeType.ALL. Hope it helps you.

@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;
jkdev
  • 11,360
  • 15
  • 54
  • 77
Kavitha yadav
  • 555
  • 5
  • 5
17

Here you have used native and assigning value to the primary key, in native primary key is auto generated.

Hence the issue is coming.

peterh
  • 11,875
  • 18
  • 85
  • 108
Bibhav
  • 203
  • 2
  • 7
  • 1
    If you believe you have additional information to offer for a question that already has an accepted answer, please provide a more substantial explanation. – ChicagoRedSox Feb 26 '14 at 05:45
10

Sharing my experience as the question title is more generic. One of the reason could be that the @GeneratedValue is used for unique id, but then the id is set again.

Example

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "COURSE")
public class Course {
   
    @Id
    @GeneratedValue
    private Long id;

    private String name;

}

The following code throws exception : org.hibernate.PersistentObjectException: detached entity passed to persist:a.b.c.Course because Id is set in the constructor.

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

Solution

Avoid setting the Id , as it is auto generated (as specified in the entity class)

Course course = new Course();
course.setName("testcourse");
testEntityManager.persist(course);
jfk
  • 4,335
  • 34
  • 27
4

Most likely the problem lies outside the code you are showing us here. You are trying to update an object that is not associated with the current session. If it is not the Invoice, then maybe it is an InvoiceItem that has already been persisted, obtained from the db, kept alive in some sort of session and then you try to persist it on a new session. This is not possible. As a general rule, never keep your persisted objects alive across sessions.

The solution will ie in obtaining the whole object graph from the same session you are trying to persist it with. In a web environment this would mean:

  • Obtain the session
  • Fetch the objects you need to update or add associations to. Preferabley by their primary key
  • Alter what is needed
  • Save/update/evict/delete what you want
  • Close/commit your session/transaction

If you keep having issues post some of the code that is calling your service.

joostschouten
  • 3,863
  • 1
  • 18
  • 31
  • Thank you @joostschouten. Apparently there should not be open session prior to calling save method as I mentioned in "More Details" that I added at the bottom my original question. Is there any way that I could check if some session exists before I call save method? – WSK Jun 17 '11 at 13:44
  • Your assumption "Apparently there should not be open session prior to calling save method" is wrong. In your case you are wrapping a transaction around each save and get which means that open sessions should not occur and if they will are of no use. Your problem seems to be in the code that handles your JSON. Here you pass an invoice with invoice items that already exist (they have id's). Pass it with null id's and it will most likely work. Or have your service handeling the JSON obtain the invoiceitems from the db, add to the invoice and save them in the same session you obtained them from. – joostschouten Jun 17 '11 at 13:55
  • @joostschouten Now I am getting this error:"org.hibernate.PropertyValueException: not-null property references a null or transient value: example.forms.InvoiceItem.invoice". Could you please give me some idea? Thanks in advance – WSK Jun 17 '11 at 16:11
  • 1
    This sounds like a new question to me. You have not shared with us an important piece of code. The code that deals with the JSON, generates your model Objects and calls persist and save. This exception tells you that you are trying to persist an invoiceItem with a null Invoice. Which rightfully cannot be done. Please post the code that actually builds your model objects. – joostschouten Jun 17 '11 at 16:17
  • @joostschouten This make sense to me but the problem is I am using a framework "qooxdoo" for JSON and creating RPC call to server where I have an RPC server utility from same framework installed. So everything is wrapped into framework classes. It may not be practical to extract and post thousands of lines. On the other hand we can watch "theInvoice" object in server side that has been created? or by showing hibernate debug/trace info? – WSK Jun 17 '11 at 16:45
  • I am not familiar with that framework but I would not be surprised if then your problem is the way you deal with transactions. If this framework get's each item by their id wrapped in a transaction and is then stored in another transaction you get the exception you are experiencing. Read up on how to deal with transactions in qooxdoo and see if you can setup a transaction per request in stead of one per DOA call. – joostschouten Jun 18 '11 at 11:35
3

I had the "same" problem because I was writting

@GeneratedValue(strategy = GenerationType.IDENTITY)

I deleted that line due that I do not need it at the moment, I was testing with objects and so. I think it is <generator class="native" /> in your case

I do not have any controller and my API is not being accessed, it is only for testing (at the moment).

2

For JPA fixed using EntityManager merge() instead of persist()

EntityManager em = getEntityManager();
    try {
        em.getTransaction().begin();
        em.merge(fieldValue);
        em.getTransaction().commit();
    } catch (Exception e) {
        //do smthng
    } finally {
        em.close();
    }
JeSa
  • 559
  • 4
  • 11
2

Two solutions

  1. use merge if you want to update the object
  2. use save if you want to just save new object (make sure identity is null to let hibernate or database generate it)
  3. if you are using mapping like
@OneToOne(fetch = FetchType.EAGER,cascade=CascadeType.ALL)
@JoinColumn(name = "stock_id")

Then use CascadeType.ALL to CascadeType.MERGE

Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
Shahid Hussain Abbasi
  • 2,508
  • 16
  • 10
0

If you use from Lombok. You write

@ToString.Exclude

where you write relotionships

0

If you want to associate an existing Object with another newly created object, you should use CascadeType.MERGE

Abbas
  • 51
  • 4