org.hibernate.PersistingObjectException: thực thể tách rời được truyền để tồn tại


89

Tôi đã viết thành công ví dụ con đầu tiên của mình với hibernate. Sau vài ngày, tôi lấy lại và nâng cấp một số thư viện. Không chắc tôi đã làm gì nhưng tôi không bao giờ có thể khiến nó chạy lại. Ai đó sẽ giúp tôi tìm ra điều gì sai trong mã đang trả về thông báo lỗi sau:

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)

lập bản đồ ngủ đông:

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

CHỈNH SỬA: 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;
    }
}

CHỈNH SỬA: Đối tượng JSON được gửi từ máy khách:

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

CHỈNH SỬA: Một số chi tiết:
Tôi đã cố gắng lưu hóa đơn bằng hai cách sau:

  1. Được tạo thủ công đối tượng json đã đề cập ở trên và chuyển nó đến phiên mới của máy chủ. Trong trường hợp này, hoàn toàn không có hoạt động nào được thực hiện trước khi gọi phương thức lưu vì vậy sẽ không có bất kỳ phiên nào đang mở ngoại trừ phiên được mở trong phương thức lưu

  2. Đã tải dữ liệu hiện có bằng cách sử dụng phương thức getInvoice và chúng đã chuyển cùng một dữ liệu sau khi xóa giá trị khóa. Điều này cũng vậy, tôi tin rằng nên đóng phiên trước khi lưu vì giao dịch đang được cam kết trong phương thức getInvoice.

Trong cả hai trường hợp, tôi đều nhận được thông báo lỗi giống nhau buộc tôi phải tin rằng có gì đó không ổn với tệp cấu hình ngủ đông hoặc các lớp thực thể hoặc phương thức lưu.

Vui lòng cho tôi biết nếu tôi nên cung cấp thêm chi tiết

Câu trả lời:


119

Bạn không cung cấp nhiều chi tiết liên quan nên tôi sẽ đoán rằng bạn đã gọi getInvoicevà sau đó bạn sử dụng đối tượng kết quả để đặt một số giá trị và gọi savevới giả định rằng các thay đổi đối tượng của bạn sẽ được lưu.

Tuy nhiên, persisthoạt động này dành cho các đối tượng tạm thời hoàn toàn mới và nó không thành công nếu id đã được gán. Trong trường hợp của bạn, bạn có thể muốn gọi saveOrUpdatethay vì persist.

Bạn có thể tìm thấy một số thảo luận và tài liệu tham khảo ở đây "thực thể tách rời được chuyển sang lỗi vẫn tồn tại" với mã JPA / EJB


Cảm ơn bạn @Alex Gitelman. Tôi đã thêm một số chi tiết ở cuối câu hỏi ban đầu của mình. Nó có giúp hiểu được vấn đề của tôi không? hoặc vui lòng cho tôi biết những chi tiết khác sẽ hữu ích.
WSK

7
tài liệu tham khảo của bạn đã giúp tôi tìm ra sai lầm ngu ngốc. Tôi đã không gửi giá trị null cho "itemId" là khóa chính trong bảng con. Vì vậy, hibernate đã giả định rằng đối tượng đã tồn tại trong một số phiên. Cảm ơn bạn đã tư vấn
WSK

Bây giờ tôi nhận được lỗi này: "org.hibernate.PropertyValueException: thuộc tính not-null tham chiếu đến giá trị null hoặc tạm thời: example.forms.InvoiceItem.invoice". Bạn có thể vui lòng cho tôi một số gợi ý? Cảm ơn trước
WSK

Bạn phải có Hóa đơn ở trạng thái tồn tại, không phải tạm thời. Điều đó có nghĩa là id đó phải được gán cho nó rồi. Vì vậy, lưu Invoicetrước, vì vậy nó sẽ nhận được id và sau đó lưu InvoiceItem. Bạn cũng có thể chơi với xếp tầng.
Alex Gitelman

13

Ở đây bạn đã sử dụng gốc và gán giá trị cho khóa chính, trong khóa chính gốc được tạo tự động.

Do đó, vấn đề đang đến.


1
Nếu bạn tin rằng bạn có thêm thông tin để cung cấp cho một câu hỏi đã có câu trả lời được chấp nhận, vui lòng cung cấp một lời giải thích đáng kể hơn.
ChicagoRedSox

8

Điều này tồn tại trong quan hệ @ManyToOne. Tôi đã giải quyết vấn đề này bằng cách chỉ sử dụng CascadeType.MERGE thay vì CascadeType.PERSIST hoặc CascadeType.ALL. Hy vọng nó sẽ giúp bạn.

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

Giải pháp:

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

4

Rất có thể vấn đề nằm ngoài mã bạn đang hiển thị cho chúng tôi ở đây. Bạn đang cố gắng cập nhật một đối tượng không được liên kết với phiên hiện tại. Nếu nó không phải là Invoice, thì có thể nó là một InvoiceItem đã được tồn tại, thu được từ db, vẫn tồn tại trong một số loại phiên và sau đó bạn cố gắng duy trì nó trong một phiên mới. Điều này là không thể. Theo nguyên tắc chung, không bao giờ giữ cho các đối tượng liên tục của bạn tồn tại trong các phiên.

Giải pháp tức là sẽ có được toàn bộ đồ thị đối tượng từ cùng một phiên mà bạn đang cố gắng duy trì nó. Trong môi trường web, điều này có nghĩa là:

  • Có được phiên
  • Tìm nạp các đối tượng bạn cần cập nhật hoặc thêm liên kết vào. Ưu tiên bằng khóa chính của họ
  • Thay đổi những gì cần thiết
  • Lưu / cập nhật / loại bỏ / xóa những gì bạn muốn
  • Đóng / cam kết phiên / giao dịch của bạn

Nếu bạn tiếp tục gặp sự cố, hãy đăng một số mã đang gọi dịch vụ của bạn.


Cảm ơn bạn @joostschouten. Rõ ràng không nên có phiên mở trước khi gọi phương thức lưu như tôi đã đề cập trong "Chi tiết khác" mà tôi đã thêm ở cuối câu hỏi ban đầu của mình. Có cách nào để tôi có thể kiểm tra xem một số phiên có tồn tại hay không trước khi tôi gọi phương thức lưu?
WSK

Giả định của bạn "Rõ ràng không nên có phiên mở trước khi gọi phương thức lưu" là sai. Trong trường hợp của bạn, bạn đang kết thúc một giao dịch xung quanh mỗi lần lưu và nhận được điều đó có nghĩa là các phiên mở sẽ không xảy ra và nếu chúng sẽ không có ích. Vấn đề của bạn dường như nằm ở mã xử lý JSON của bạn. Ở đây bạn chuyển một hóa đơn với các mục hóa đơn đã tồn tại (chúng có id). Chuyển nó với null id's và nó rất có thể sẽ hoạt động. Hoặc yêu cầu dịch vụ của bạn xử lý JSON lấy các hóa đơn từ db, thêm vào hóa đơn và lưu chúng trong cùng một phiên mà bạn lấy chúng.
joostschouten

@joostschouten Bây giờ tôi gặp lỗi này: "org.hibernate.PropertyValueException: thuộc tính not-null tham chiếu đến giá trị null hoặc thoáng qua: example.forms.InvoiceItem.invoice". Bạn có thể vui lòng cho tôi một số ý tưởng? Cảm ơn trước
WSK

1
Điều này nghe có vẻ như là một câu hỏi mới đối với tôi. Bạn chưa chia sẻ với chúng tôi một đoạn mã quan trọng. Mã giao dịch với JSON, tạo các Đối tượng và cuộc gọi mô hình của bạn vẫn tồn tại và lưu. Ngoại lệ này cho bạn biết rằng bạn đang cố gắng duy trì một Mục hóa đơn có Hóa đơn rỗng. Mà một cách chính đáng không thể được thực hiện. Vui lòng đăng mã thực sự xây dựng các đối tượng mô hình của bạn.
joostschouten

@joostschouten Điều này có ý nghĩa với tôi nhưng vấn đề là tôi đang sử dụng một khuôn khổ "qooxdoo" cho JSON và tạo lệnh gọi RPC tới máy chủ nơi tôi đã cài đặt tiện ích máy chủ RPC từ cùng một khuôn khổ. Vì vậy, mọi thứ được gói vào các lớp khung. Nó có thể không thực tế để trích xuất và đăng hàng ngàn dòng. Mặt khác, chúng ta có thể xem đối tượng "theInvoice" ở phía máy chủ đã được tạo? hoặc bằng cách hiển thị thông tin gỡ lỗi / theo dõi ngủ đông?
WSK

2

Hai giải pháp 1. sử dụng hợp nhất nếu bạn muốn cập nhật đối tượng 2. sử dụng lưu nếu bạn chỉ muốn lưu đối tượng mới (đảm bảo danh tính là null để cho phép ngủ đông hoặc cơ sở dữ liệu tạo ra nó) 3. nếu bạn đang sử dụng ánh xạ như
@OneToOne ( fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinColumn (name = "stock_id")

Sau đó sử dụng CascadeType.ALL để CascadeType.MERGE

cảm ơn Shahid Abbasi


0

Đối với JPA được cố định bằng cách sử dụng EntityManager merge () thay vì stay ()

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

0

Tôi đã gặp vấn đề "tương tự" vì tôi đang viết

@GeneratedValue(strategy = GenerationType.IDENTITY)

Tôi đã xóa dòng đó do tôi không cần nó vào lúc này, tôi đang thử nghiệm với các đối tượng và như vậy. Tôi nghĩ nó là <generator class="native" />trong trường hợp của bạn

Tôi không có bất kỳ bộ điều khiển nào và API của tôi không được truy cập, nó chỉ để thử nghiệm (vào lúc này).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.