org.hibernate.PersistentObjectException: เอนทิตีแยกที่ส่งผ่านไปยังคงอยู่


90

ฉันเขียนตัวอย่างเด็กต้นแบบคนแรกของฉันด้วยโหมดไฮเบอร์เนตได้สำเร็จ หลังจากนั้นไม่กี่วันฉันก็เอามันอีกครั้งและอัปเกรดห้องสมุดบางส่วน ไม่แน่ใจว่าฉันทำอะไรไปบ้าง แต่ฉันไม่สามารถทำให้มันกลับมาทำงานได้อีก ใครจะช่วยฉันได้ไหมว่ามีอะไรผิดพลาดในรหัสที่ส่งคืนข้อความแสดงข้อผิดพลาดต่อไปนี้:

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

แก้ไข: 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;
    }
}

แก้ไข:ออบเจ็กต์ JSON ที่ส่งจากไคลเอนต์:

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

แก้ไข:รายละเอียดบางส่วน:
ฉันได้พยายามบันทึกใบแจ้งหนี้โดยทำตามสองวิธี:

  1. ประดิษฐ์วัตถุ json ที่กล่าวถึงข้างต้นด้วยตนเองและส่งต่อไปยังเซสชันใหม่ของเซิร์ฟเวอร์ ในกรณีนี้ไม่มีกิจกรรมใด ๆ เกิดขึ้นก่อนที่จะเรียกวิธีบันทึกดังนั้นจึงไม่ควรมีเซสชันที่เปิดอยู่ยกเว้นเซสชันที่เปิดในวิธีบันทึก

  2. โหลดข้อมูลที่มีอยู่โดยใช้เมธอด getInvoice และส่งผ่านข้อมูลเดียวกันหลังจากลบค่าคีย์ เช่นกันฉันเชื่อว่าควรปิดเซสชันก่อนที่จะบันทึกเนื่องจากมีการทำธุรกรรมในเมธอด getInvoice

ในทั้งสองกรณีฉันได้รับข้อความแสดงข้อผิดพลาดเดียวกันที่บังคับให้ฉันเชื่อว่ามีบางอย่างผิดปกติกับไฟล์การกำหนดค่าไฮเบอร์เนตหรือคลาสเอนทิตีหรือวิธีบันทึก

โปรดแจ้งให้เราทราบหากฉันควรให้รายละเอียดเพิ่มเติม

คำตอบ:


120

คุณไม่ได้ให้รายละเอียดที่เกี่ยวข้องมากนักดังนั้นฉันจะเดาว่าคุณเรียกgetInvoiceแล้วคุณใช้ออบเจ็กต์ผลลัพธ์เพื่อกำหนดค่าบางอย่างและเรียกsaveโดยสมมติว่าการเปลี่ยนแปลงวัตถุของคุณจะได้รับการบันทึก

อย่างไรก็ตามpersistการดำเนินการมีไว้สำหรับออบเจ็กต์ชั่วคราวใหม่เอี่ยมและจะล้มเหลวหากกำหนด id ไว้แล้ว ในกรณีของคุณคุณอาจต้องการที่จะเรียกแทนsaveOrUpdatepersist

คุณสามารถค้นหาการสนทนาและการอ้างอิงบางส่วนได้ที่นี่"เอนทิตีแยกส่งไปยังคงมีข้อผิดพลาด" พร้อมรหัส JPA / EJB


ขอบคุณ @Alex Gitelman ฉันได้เพิ่มรายละเอียดบางอย่างที่ด้านล่างของคำถามเดิมของฉัน ช่วยให้เข้าใจปัญหาของฉันหรือไม่? หรือโปรดแจ้งให้เราทราบรายละเอียดอื่น ๆ ที่จะเป็นประโยชน์
WSK

7
การอ้างอิงของคุณช่วยให้ฉันพบข้อผิดพลาดโง่ ๆ ฉันไม่ได้ส่งค่า null สำหรับ "itemId" ที่เป็นคีย์หลักในตารางลูก ดังนั้นการจำศีลจึงตั้งสมมติฐานว่ามีวัตถุอยู่แล้วในบางเซสชัน ขอบคุณสำหรับคำแนะนำ
WSK

ตอนนี้ฉันได้รับข้อผิดพลาดนี้: "org.hibernate.PropertyValueException: คุณสมบัติที่ไม่เป็นค่าว่างอ้างถึงค่า null หรือชั่วคราว: example.forms.InvoiceItem.invoice" คุณช่วยบอกฉันหน่อยได้ไหม ขอบคุณล่วงหน้า
WSK

คุณต้องมีใบแจ้งหนี้ในสถานะคงอยู่ไม่ใช่ชั่วคราว นั่นหมายความว่าต้องกำหนด id ให้อยู่แล้ว ดังนั้นบันทึกครั้งแรกจึงจะได้รับรหัสแล้วบันทึกInvoice InvoiceItemคุณอาจเล่นแบบเรียงซ้อน
Alex Gitelman

14

ที่นี่คุณได้ใช้เนทีฟและกำหนดค่าให้กับคีย์หลักในคีย์หลักเนทีฟจะถูกสร้างขึ้นโดยอัตโนมัติ

ดังนั้นปัญหากำลังมา


1
หากคุณเชื่อว่าคุณมีข้อมูลเพิ่มเติมที่จะเสนอสำหรับคำถามที่มีคำตอบที่เป็นที่ยอมรับแล้วโปรดให้คำอธิบายที่เป็นสาระสำคัญมากขึ้น
ChicagoRedSox

11

สิ่งนี้มีอยู่ในความสัมพันธ์ @ManyToOne ฉันแก้ไขปัญหานี้โดยใช้ CascadeType.MERGE แทน CascadeType.PERSIST หรือ CascadeType.ALL หวังว่าจะช่วยคุณได้

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

วิธีการแก้:

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

4

ปัญหาส่วนใหญ่อยู่นอกรหัสที่คุณแสดงให้เราเห็นที่นี่ คุณกำลังพยายามอัปเดตวัตถุที่ไม่เกี่ยวข้องกับเซสชันปัจจุบัน หากไม่ใช่ใบแจ้งหนี้อาจเป็นไปได้ว่าเป็น InvoiceItem ที่ได้รับจากฐานข้อมูลที่ยังมีชีวิตอยู่ในเซสชันบางประเภทจากนั้นคุณจะพยายามคงอยู่ในเซสชันใหม่ นี้เป็นไปไม่ได้ ตามกฎทั่วไปอย่าให้วัตถุคงอยู่ของคุณคงอยู่ตลอดเซสชัน

วิธีแก้ปัญหาคือในการรับกราฟวัตถุทั้งหมดจากเซสชันเดียวกับที่คุณพยายามจะคงอยู่ด้วย ในสภาพแวดล้อมเว็บหมายความว่า:

  • รับเซสชั่น
  • ดึงวัตถุที่คุณต้องการอัปเดตหรือเพิ่มการเชื่อมโยง เด่นกว่าด้วยคีย์หลัก
  • เปลี่ยนแปลงสิ่งที่จำเป็น
  • บันทึก / อัปเดต / ขับไล่ / ลบสิ่งที่คุณต้องการ
  • ปิด / กระทำเซสชัน / ธุรกรรมของคุณ

หากคุณยังคงมีปัญหาให้โพสต์รหัสที่เรียกใช้บริการของคุณ


ขอบคุณ @joostschouten เห็นได้ชัดว่าไม่ควรเปิดเซสชันก่อนที่จะเรียกวิธีการบันทึกตามที่ฉันกล่าวไว้ใน "รายละเอียดเพิ่มเติม" ที่ฉันได้เพิ่มไว้ที่ด้านล่างของคำถามเดิมของฉัน มีวิธีใดบ้างที่ฉันสามารถตรวจสอบได้ว่ามีบางเซสชันอยู่ก่อนที่จะเรียกใช้วิธีบันทึกหรือไม่
WSK

สมมติฐานของคุณ "เห็นได้ชัดว่าไม่ควรเปิดเซสชันก่อนที่จะเรียกวิธีการบันทึก" ผิด ในกรณีของคุณคุณกำลังห่อธุรกรรมรอบ ๆ การบันทึกและรับแต่ละครั้งซึ่งหมายความว่าเซสชันที่เปิดอยู่ไม่ควรเกิดขึ้นและจะไม่มีประโยชน์หรือไม่ ปัญหาของคุณน่าจะอยู่ที่โค้ดที่จัดการ JSON ของคุณ ที่นี่คุณส่งใบแจ้งหนี้พร้อมรายการใบแจ้งหนี้ที่มีอยู่แล้ว (มีรหัส) ส่งผ่านด้วยรหัสว่างและมันจะใช้ได้มากที่สุด หรือให้บริการของคุณจัดการ JSON รับรายการใบแจ้งหนี้จากฐานข้อมูลเพิ่มในใบแจ้งหนี้และบันทึกไว้ในเซสชันเดียวกับที่คุณได้รับจาก
joostschouten

@joostschouten ตอนนี้ฉันได้รับข้อผิดพลาดนี้: "org.hibernate.PropertyValueException: คุณสมบัติที่ไม่เป็นค่าว่างอ้างถึงค่าว่างหรือค่าชั่วคราว: example.forms.InvoiceItem.invoice" ขอความคิดเห็นหน่อยได้ไหม ขอบคุณล่วงหน้า
WSK

1
นี่เป็นคำถามใหม่สำหรับฉัน คุณไม่ได้แบ่งปันรหัสชิ้นสำคัญกับเรา รหัสที่เกี่ยวข้องกับ JSON สร้างออบเจ็กต์โมเดลของคุณและการเรียกยังคงอยู่และบันทึก ข้อยกเว้นนี้จะบอกคุณว่าคุณกำลังพยายามคง InvoiceItem ด้วยใบแจ้งหนี้ที่เป็นโมฆะ ซึ่งโดยชอบธรรมไม่สามารถทำได้. โปรดโพสต์รหัสที่ใช้สร้างโมเดลออบเจ็กต์ของคุณ
joostschouten

@joostschouten สิ่งนี้สมเหตุสมผลสำหรับฉัน แต่ปัญหาคือฉันใช้เฟรมเวิร์ก "qooxdoo" สำหรับ JSON และสร้างการเรียก RPC ไปยังเซิร์ฟเวอร์ที่ฉันมียูทิลิตี้เซิร์ฟเวอร์ RPC จากเฟรมเวิร์กเดียวกันติดตั้งอยู่ ดังนั้นทุกอย่างจึงรวมอยู่ในคลาสเฟรมเวิร์ค การแยกและโพสต์ข้อความหลายพันบรรทัดอาจใช้ไม่ได้จริง ในทางกลับกันเราสามารถดูวัตถุ "theInvoice" ในฝั่งเซิร์ฟเวอร์ที่สร้างขึ้น? หรือโดยการแสดงข้อมูลการดีบัก / การติดตามไฮเบอร์เนต?
WSK

2

วิธีแก้ปัญหาสองวิธี 1. ใช้การผสานหากคุณต้องการอัปเดตอ็อบเจ็กต์ 2. ใช้บันทึกหากคุณต้องการบันทึกอ็อบเจ็กต์ใหม่ (ตรวจสอบให้แน่ใจว่าข้อมูลประจำตัวเป็นโมฆะเพื่อให้ไฮเบอร์เนตหรือฐานข้อมูลสร้างขึ้น) 3. หากคุณใช้การแมปเช่น
@OneToOne ( fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinColumn (name = "stock_id")

จากนั้นใช้ CascadeType.ALL เพื่อ CascadeType.MERGE

ขอบคุณ Shahid Abbasi


1

ฉันมีปัญหา "เดียวกัน" เพราะฉันกำลังเขียน

@GeneratedValue(strategy = GenerationType.IDENTITY)

ฉันลบบรรทัดนั้นออกเนื่องจากฉันไม่ต้องการมันในขณะนี้ฉันกำลังทดสอบกับวัตถุและอื่น ๆ ฉันคิดว่ามันอยู่<generator class="native" />ในกรณีของคุณ

ฉันไม่มีคอนโทรลเลอร์และ API ของฉันไม่ได้ถูกเข้าถึงมันเป็นเพียงการทดสอบเท่านั้น (ในขณะนี้)


0

สำหรับ JPA คงที่โดยใช้ EntityManager merge () แทน persist ()

EntityManager em = getEntityManager();
    try {
        em.getTransaction().begin();
        em.merge(fieldValue);
        em.getTransaction().commit();
    } catch (Exception e) {
        //do smthng
    } finally {
        em.close();
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.