org.hibernate.PersistentObjectException: odłączona jednostka przekazana do trwałości

89

Z powodzeniem napisałem mój pierwszy przykład dziecka-mistrza z hibernacją. Po kilku dniach wziąłem go ponownie i zaktualizowałem niektóre biblioteki. Nie jestem pewien, co zrobiłem, ale nigdy nie mogłem sprawić, by znowu działał. Czy ktoś pomoże mi dowiedzieć się, co jest nie tak w kodzie, który zwraca następujący komunikat o błędzie:

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)

mapowanie hibernacji:

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

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

EDYCJA: obiekt JSON wysłany z klienta:

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

EDYCJA: Niektóre szczegóły:
Próbowałem zapisać fakturę na dwa sposoby:

  1. Ręcznie sfabrykowany powyższy obiekt json i przekazany do nowej sesji serwera. W tym przypadku żadna czynność nie została wykonana przed wywołaniem metody save, więc nie powinno być żadnej otwartej sesji poza tą otwartą metodą save

  2. Załadowano istniejące dane przy użyciu metody getInvoice i przekazały te same dane po usunięciu wartości klucza. Uważam, że to również powinno zamknąć sesję przed zapisaniem, ponieważ transakcja jest zatwierdzana w metodzie getInvoice.

W obu przypadkach pojawia się ten sam komunikat o błędzie, który zmusza mnie do przekonania, że ​​coś jest nie tak z plikiem konfiguracyjnym hibernacji lub klasami jednostek lub metodą zapisywania.

Daj mi znać, jeśli mam podać więcej szczegółów

WSK
źródło

Odpowiedzi:

119

Nie podałeś wielu istotnych szczegółów, więc domyślam się, że wywołałeś, getInvoicea następnie użyłeś obiektu wyniku do ustawienia niektórych wartości i wywołania savez założeniem, że zmiany obiektu zostaną zapisane.

Jednak persistoperacja jest przeznaczona dla zupełnie nowych obiektów przejściowych i kończy się niepowodzeniem, jeśli identyfikator jest już przypisany. W Twoim przypadku prawdopodobnie chcesz zadzwonić saveOrUpdatezamiast persist.

Niektóre dyskusje i odniesienia można znaleźć tutaj „odłączona jednostka przekazana w celu utrwalenia błędu” z kodem JPA / EJB

Alex Gitelman
źródło
Dziękuję @Alex Gitelman. Na dole mojego pierwotnego pytania dodałem kilka szczegółów. Czy pomaga to zrozumieć mój problem? lub daj mi znać, jakie inne szczegóły byłyby pomocne.
WSK
7
Twoje odniesienie pomogło mi znaleźć głupi błąd. Wysyłałem niezerową wartość dla „itemId”, który jest kluczem podstawowym w tabeli podrzędnej. Więc hibernacja zakładała, że ​​obiekt już istnieje w jakiejś sesji. Dziękuję za radę
WSK
Teraz pojawia się ten błąd: „org.hibernate.PropertyValueException: właściwość not-null odwołuje się do wartości null lub przejściowej: example.forms.InvoiceItem.invoice”. Czy mógłbyś dać mi jakąś wskazówkę? Z góry dziękuję
WSK
Musisz mieć fakturę w stanie utrwalonym, a nie przejściowym. Oznacza to, że identyfikator musi być już do niego przypisany. Więc zapisz Invoicenajpierw, aby uzyskać id, a następnie zapisz InvoiceItem. Możesz także grać kaskadowo.
Alex Gitelman
13

Tutaj użyłeś natywnego i przypisanie wartości do klucza podstawowego, w natywnym kluczu podstawowym jest generowany automatycznie.

Stąd problem nadchodzi.

Bibhav
źródło
1
Jeśli uważasz, że masz dodatkowe informacje do zaoferowania na pytanie, na które już została zaakceptowana odpowiedź, podaj bardziej szczegółowe wyjaśnienie.
ChicagoRedSox
8

To istnieje w relacji @ManyToOne. Rozwiązałem ten problem, używając po prostu CascadeType.MERGE zamiast CascadeType.PERSIST lub CascadeType.ALL. Mam nadzieję, że ci to pomoże.

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

Rozwiązanie:

@ManyToOne(cascade = CascadeType.MERGE)
@JoinColumn(name="updated_by", referencedColumnName = "id")
private Admin admin;
Kavitha yadav
źródło
4

Najprawdopodobniej problem leży poza kodem, który nam pokazujesz. Próbujesz zaktualizować obiekt, który nie jest powiązany z bieżącą sesją. Jeśli nie jest to faktura, to może jest to element InvoiceItem, który został już utrwalony, uzyskany z bazy danych, utrzymany przy życiu w jakiejś sesji, a następnie próbujesz utrwalić go w nowej sesji. To jest niemożliwe. Zasadniczo nigdy nie utrzymuj trwałych obiektów przy życiu podczas sesji.

Rozwiązaniem będzie np. Uzyskanie całego grafu obiektu z tej samej sesji, z którą próbujesz go utrwalić. W środowisku internetowym oznaczałoby to:

  • Uzyskaj sesję
  • Pobierz obiekty, które chcesz zaktualizować lub do których chcesz dodać powiązania. Najlepiej za pomocą klucza podstawowego
  • Zmień to, co jest potrzebne
  • Zapisz / zaktualizuj / eksmituj / usuń to, co chcesz
  • Zamknij / zatwierdź swoją sesję / transakcję

Jeśli nadal masz problemy, opublikuj kod wywołujący usługę.

joostschouten
źródło
Dziękuję @joostschouten. Najwyraźniej nie powinno być otwartej sesji przed wywołaniem metody zapisywania, jak wspomniałem w "Więcej szczegółów", które dodałem na dole moje oryginalne pytanie. Czy jest jakiś sposób, żebym mógł sprawdzić, czy jakaś sesja istnieje, zanim wywołam metodę save?
WSK
Twoje założenie „Najwyraźniej nie powinno być otwartej sesji przed wywołaniem metody zapisywania” jest błędne. W twoim przypadku zawijasz transakcję wokół każdego zapisu i pobrania, co oznacza, że ​​otwarte sesje nie powinny występować i jeśli będą bezużyteczne. Wydaje się, że Twój problem tkwi w kodzie obsługującym JSON. Tutaj przekazujesz fakturę z pozycjami faktury, które już istnieją (mają identyfikatory). Przekaż go z null id i najprawdopodobniej zadziała. Lub poproś swoją usługę obsługującą JSON o pobranie elementów faktury z bazy danych, dodanie do faktury i zapisanie ich w tej samej sesji, z której je otrzymałeś.
joostschouten
@joostschouten Teraz pojawia się ten błąd: „org.hibernate.PropertyValueException: właściwość not-null odwołuje się do wartości null lub przejściowej: example.forms.InvoiceItem.invoice”. Czy mógłbyś podać mi jakiś pomysł? Z góry dziękuję
WSK
1
To brzmi dla mnie jak nowe pytanie. Nie udostępniłeś nam ważnego fragmentu kodu. Kod, który zajmuje się JSON, generuje obiekty modelu i wywołuje utrwalanie i zapisywanie. Ten wyjątek oznacza, że ​​próbujesz utrwalić fakturę faktury z pustą fakturą. Czego słusznie nie można zrobić. Proszę opublikować kod, który faktycznie buduje obiekty modelu.
joostschouten
@joostschouten Ma to dla mnie sens, ale problem polega na tym, że używam frameworka „qooxdoo” dla JSON i tworzę wywołanie RPC do serwera, na którym mam zainstalowane narzędzie serwera RPC z tej samej platformy. Więc wszystko jest opakowane w klasy frameworka. Wyodrębnianie i publikowanie tysięcy wierszy może nie być praktyczne. Z drugiej strony możemy obserwować utworzony obiekt "theInvoice" po stronie serwera? lub wyświetlając informacje debugowania / śledzenia hibernacji?
WSK
2

Dwa rozwiązania 1. użyj scalania, jeśli chcesz zaktualizować obiekt 2. użyj zapisz, jeśli chcesz po prostu zapisać nowy obiekt (upewnij się, że tożsamość jest zerowa, aby umożliwić hibernację lub wygenerowanie przez bazę danych) 3. jeśli używasz mapowania, takiego jak
@OneToOne ( fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinColumn (name = "stock_id")

Następnie użyj CascadeType.ALL do CascadeType.MERGE

dzięki Shahid Abbasi

Shahid Hussain Abbasi
źródło
0

W przypadku JPA naprawiono za pomocą EntityManager merge () zamiast persist ()

EntityManager em = getEntityManager();
    try {
        em.getTransaction().begin();
        em.merge(fieldValue);
        em.getTransaction().commit();
    } catch (Exception e) {
        //do smthng
    } finally {
        em.close();
    }
JeSa
źródło
0

Miałem „ten sam” problem, ponieważ pisałem

@GeneratedValue(strategy = GenerationType.IDENTITY)

Usunąłem tę linię, ponieważ w tej chwili jej nie potrzebuję, testowałem z obiektami i tak dalej. Myślę, że tak jest <generator class="native" />w twoim przypadku

Nie mam żadnego kontrolera, a moje API nie jest dostępne, służy tylko do testowania (w tej chwili).

Miguel Avila
źródło