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

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.

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

Пожалуйста, дайте мне знать, если мне нужно предоставить более подробную информацию

Ответы:


119

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

Однако persistоперация предназначена для новых временных объектов и завершается ошибкой, если идентификатор уже назначен. В вашем случае вы, вероятно, захотите позвонить saveOrUpdateвместо persist.

Вы можете найти некоторые обсуждения и ссылки здесь «Отдельный объект передан для сохранения ошибки» с кодом JPA / EJB


Спасибо @Alex Gitelman. Я добавил некоторые детали в конце своего исходного вопроса. Это помогает разобраться в моей проблеме? или дайте мне знать, какие еще подробности будут вам полезны.
WSK

7
Ваша ссылка помогла мне найти глупую ошибку. Я отправлял ненулевое значение для «itemId», который является первичным ключом в дочерней таблице. Итак, hibernate предполагал, что объект уже существует в каком-то сеансе. Спасибо за совет
WSK

Теперь я получаю эту ошибку: «org.hibernate.PropertyValueException: свойство not-null ссылается на нулевое или временное значение: example.forms.InvoiceItem.invoice». Не могли бы вы мне намекнуть? Заранее спасибо
WSK

У вас должен быть счет-фактура в постоянном, а не временном состоянии. Это означает, что ему уже должен быть присвоен идентификатор. Так что Invoiceсначала сохраните , чтобы он получил идентификатор, а затем сохраните InvoiceItem. Вы также можете поиграть с каскадом.
Alex Gitelman

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, который уже был сохранен, получен из базы данных, сохранен в каком-то сеансе, а затем вы пытаетесь сохранить его в новом сеансе. Это невозможно. Как правило, никогда не поддерживайте постоянные объекты в рабочем состоянии между сеансами.

Решение будет, например, в получении всего графа объекта из того же сеанса, в котором вы пытаетесь его сохранить. В веб-среде это будет означать:

  • Получить сеанс
  • Получите объекты, которые вам нужно обновить или добавить ассоциации. Предпочтительно по первичному ключу
  • Измените то, что нужно
  • Сохраните / обновите / выселите / удалите то, что хотите
  • Закройте / зафиксируйте сеанс / транзакцию

Если у вас по-прежнему возникают проблемы, опубликуйте код, который вызывает вашу службу.


Спасибо @joostschouten. По-видимому, не должно быть открытого сеанса до вызова метода сохранения, как я упоминал в разделе «Подробнее», который я добавил внизу свой исходный вопрос. Есть ли способ проверить, существует ли какой-либо сеанс, прежде чем я вызову метод сохранения?
WSK

Ваше предположение «По-видимому, не должно быть открытого сеанса до вызова метода сохранения» неверно. В вашем случае вы обертываете транзакцию вокруг каждого сохранения и получения, что означает, что открытые сеансы не должны происходить, и если они будут бесполезны. Кажется, ваша проблема в коде, который обрабатывает ваш JSON. Здесь вы передаете счет с уже существующими элементами счета (у них есть идентификаторы). Передайте его с нулевым идентификатором, и он, скорее всего, сработает. Или попросите вашу службу, обрабатывающую JSON, получить элементы счета из базы данных, добавить в счет и сохранить их в том же сеансе, из которого вы их получили.
joostschouten

@joostschouten Теперь я получаю эту ошибку: «org.hibernate.PropertyValueException: свойство not-null ссылается на нулевое или временное значение: example.forms.InvoiceItem.invoice». Не могли бы вы дать мне какое-нибудь представление? Заранее спасибо
WSK

1
Для меня это звучит как новый вопрос. Вы не поделились с нами важным фрагментом кода. Код, имеющий дело с JSON, генерирует объекты вашей модели, и вызовы сохраняются и сохраняются. Это исключение сообщает вам, что вы пытаетесь сохранить invoiceItem с нулевым Invoice. Что по праву невозможно сделать. Опубликуйте код, который фактически создает объекты вашей модели.
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

спасибо Шахид Аббаси


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.