org.hibernate.PersistentObjectException: відокремлена сутність передана для збереження


89

Я успішно написав свій перший приклад для старшої дитини із сплячим режимом. Через кілька днів я взяв його знову і модернізував деякі бібліотеки. Не впевнений, що я зробив, але я ніколи не міг змусити його запуститися знову. Хтось допоможе мені зрозуміти, що не так у коді, який повертає таке повідомлення про помилку:

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

Рахунок.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, надісланий від клієнта:

{"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: Деякі деталі:
Я спробував зберегти рахунок-фактуру двома способами:

  1. Виготовлений вручну згаданий вище json-об'єкт вручну та переданий до нового сеансу сервера. У цьому випадку до виклику методу збереження не виконувалось абсолютно жодної діяльності, тому не повинно бути жодного відкритого сеансу, крім того, що відкрився в методі збереження

  2. Завантажив існуючі дані за допомогою методу getInvoice, і вони передали ті самі дані після видалення значення ключа. Я також вважаю, що слід закрити сесію перед збереженням, оскільки транзакція здійснюється методом getInvoice.

В обох випадках я отримую одне і те ж повідомлення про помилку, яке змушує мене вважати, що щось не так або з файлом конфігурації сплячого режиму, або з класами сутності, або методом збереження.

Будь ласка, повідомте мене, якщо я повинен надати більше деталей

Відповіді:


119

Ви не надали багато релевантних деталей, тому я здогадуюсь, що ви зателефонували, getInvoiceа потім використовували об'єкт результату для встановлення деяких значень та виклику saveз припущенням, що зміни вашого об'єкта будуть збережені.

Однак persistоперація призначена для абсолютно нових перехідних об'єктів, і вона не вдається, якщо ідентифікатор вже призначений. У вашому випадку ви, ймовірно, хочете зателефонувати saveOrUpdateзамість persist.

Ви можете знайти обговорення та посилання тут "відокремлений об'єкт, переданий на постійну помилку" з кодом JPA / EJB


Дякую @Alex Gitelman. Я додав деякі подробиці внизу свого початкового запитання. Це допомагає зрозуміти мою проблему? або, будь ласка, повідомте мені, які інші деталі були б корисними.
WSK,

7
Ваше посилання допомогло мені знайти дурну помилку. Я надсилав не нульове значення для "itemId", що є первинним ключем у дочірній таблиці. Отже, сплячий режим робив припущення, що об’єкт вже існує в деякому сеансі. Дякую за пораду
WSK

Тепер я отримую таку помилку: "org.hibernate.PropertyValueException: властивість not-null посилається на нульове або перехідне значення: example.forms.InvoiceItem.invoice". Не могли б ви дати мені якусь підказку? Попереднє спасибі
WSK,

Ви повинні мати рахунок-фактуру в постійному стані, а не в минулому. Це означає, що йому вже потрібно призначити ідентифікатор. Тож збережіть Invoiceспочатку, щоб він отримав ідентифікатор, а потім збереже InvoiceItem. Ви також можете грати з каскадом.
Алекс Гітельман,

13

Тут ви використовували власний і присвоюючи значення первинному ключу, в рідному первинному ключі створюється автоматично.

Звідси питання виходить.


1
Якщо ви вважаєте, що маєте додаткову інформацію щодо запитання, на яке вже прийнято відповідь, надайте більш суттєве пояснення.
ChicagoRedSox

8

Це існує у відношенні @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

Швидше за все, проблема полягає поза кодом, який ви нам тут показуєте. Ви намагаєтесь оновити об’єкт, який не пов’язаний із поточним сеансом. Якщо це не Invoice, то, можливо, це InvoiceItem, який уже зберігався, отриманий з db, залишався живим в якомусь сеансі, а потім ви намагаєтесь зберегти його на новому сеансі. Це неможливо. Як правило, ніколи не тримайте збережені об'єкти живими протягом сеансів.

Рішення полягає у отриманні цілого графіку об’єкта з того самого сеансу, з яким ви намагаєтесь його зберегти. У веб-середовищі це означало б:

  • Отримати сеанс
  • Завантажте об’єкти, які вам потрібно оновити, або додайте асоціації. Переважно за їх первинним ключем
  • Змініть те, що потрібно
  • Збережіть / оновіть / виселіть / видаліть те, що хочете
  • Закрийте / зафіксуйте сеанс / транзакцію

Якщо у вас постійно виникають проблеми, опублікуйте частину коду, який викликає вашу службу.


Дякую @joostschouten. Очевидно, не повинно бути відкритого сеансу до виклику методу збереження, як я вже згадував у "Докладніше", який я додав внизу свого початкового питання. Чи є спосіб, яким я можу перевірити, чи існує якийсь сеанс, перш ніж я викликаю метод збереження?
WSK,

Ваше припущення "Мабуть, не повинно бути відкритого сеансу до виклику методу збереження", є помилковим. У вашому випадку ви обгортаєте транзакцію навколо кожного збереження і отримуєте, що означає, що відкриті сеанси не повинні відбуватися, і якщо вони будуть корисними. Здається, ваша проблема в коді, який обробляє ваш JSON. Тут ви передаєте рахунок-фактуру з елементами рахунків-фактур, які вже існують (вони мають ідентифікатори). Передайте його з нульовими ідентифікаторами, і це, швидше за все, спрацює. Або попросіть вашу службу обробити JSON отримати елементи рахунків-фактур у базі даних, додати до рахунку-фактури та зберегти їх у тому самому сеансі, з якого ви їх отримали.
joostschouten

@joostschouten Тепер я отримую таку помилку: "org.hibernate.PropertyValueException: властивість not-null посилається на нульове або перехідне значення: example.forms.InvoiceItem.invoice". Не могли б ви дати мені якусь ідею? Попереднє спасибі
WSK,

1
Для мене це звучить як нове запитання. Ви не поділилися з нами важливою частиною коду. Код, який має справу з JSON, генерує ваші об'єкти моделі, а дзвінки зберігаються та зберігаються. Цей виняток повідомляє вам, що ви намагаєтеся зберегти рахунок-фактуру з нульовим рахунком-фактурою. Що по праву неможливо зробити. Будь ласка, опублікуйте код, який фактично створює ваші об'єкти моделі.
joostschouten

@joostschouten Це для мене має сенс, але проблема полягає в тому, що я використовую фреймворк "qooxdoo" для JSON і створюю виклик RPC до сервера, де у мене встановлена ​​утиліта сервера RPC з тієї ж основи. Отже, все обернене у фреймворкові класи. Витягнути та розмістити тисячі рядків може бути непрактично. З іншого боку, ми можемо спостерігати за об’єктом «theInvoice» на стороні сервера, який було створено? або показуючи сплячий режим налагодження / трасування?
WSK,

2

Два рішення: 1. використовуйте merge, якщо ви хочете оновити об’єкт. 2. використовуйте save, якщо ви хочете просто зберегти новий об’єкт (переконайтесь, що ідентичність нульова, щоб дозволити сплячому режиму або базі даних його генерувати) 3. якщо ви використовуєте відображення, як
@OneToOne fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinColumn (name = "stock_id")

Потім використовуйте CascadeType.ALL для CascadeType.MERGE

дякую Шахіду Аббасі


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

0

У мене була "та сама" проблема, бо я писав

@GeneratedValue(strategy = GenerationType.IDENTITY)

Я видалив цей рядок через те, що наразі він мені не потрібен, я тестував з об’єктами тощо. Я думаю, що це <generator class="native" />у вашому випадку

У мене немає жодного контролера, і до мого API немає доступу, це лише для тестування (на даний момент).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.