org.hibernate.PersistentObjectException: entidade separada passada para persistir

89

Escrevi com sucesso meu primeiro exemplo de filho mestre com hibernação. Depois de alguns dias, peguei novamente e atualizei algumas bibliotecas. Não tenho certeza do que eu fiz, mas eu nunca poderia fazê-lo funcionar novamente. Alguém poderia me ajudar a descobrir o que há de errado no código que está retornando a seguinte mensagem de erro:

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)

mapeamento de hibernação:

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

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

EDITAR: objeto JSON enviado do cliente:

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

EDITAR: Alguns detalhes:
tentei salvar a fatura de duas maneiras:

  1. O objeto json mencionado acima foi fabricado manualmente e o passou para uma nova sessão do servidor. Neste caso, absolutamente nenhuma atividade foi realizada antes de chamar o método save, portanto, não deve haver nenhuma sessão aberta exceto aquela aberta no método save

  2. Dados existentes carregados usando o método getInvoice e eles transmitiram os mesmos dados após remover o valor da chave. Também acredito que deve fechar a sessão antes de salvar, pois a transação está sendo confirmada no método getInvoice.

Em ambos os casos, estou recebendo a mesma mensagem de erro que está me forçando a acreditar que algo está errado com o arquivo de configuração de hibernação ou classes de entidade ou método de salvamento.

Informe-me se devo fornecer mais detalhes

WSK
fonte

Respostas:

119

Você não forneceu muitos detalhes relevantes, então suponho que você chamou getInvoicee então usou o objeto de resultado para definir alguns valores e chamar savecom a suposição de que as alterações do objeto serão salvas.

No entanto, a persistoperação é destinada a novos objetos transitórios e falha se o id já estiver atribuído. No seu caso, você provavelmente deseja ligar em saveOrUpdatevez de persist.

Você pode encontrar algumas discussões e referências aqui "entidade separada passada para persistir erro" com código JPA / EJB

Alex Gitelman
fonte
Obrigado @Alex Gitelman. Eu adicionei alguns detalhes na parte inferior da minha pergunta original. Isso ajuda a entender meu problema? ou diga-me quais outros detalhes seriam úteis.
WSK de
7
sua referência me ajudou a encontrar um erro estúpido. Eu estava enviando um valor não nulo para "itemId" que é a chave primária na tabela filho. Portanto, o Hibernate estava assumindo que o objeto já existe em alguma sessão. Obrigado pelo conselho
WSK
Agora estou recebendo este erro: "org.hibernate.PropertyValueException: a propriedade not-null faz referência a um valor nulo ou transiente: example.forms.InvoiceItem.invoice". Você poderia me dar alguma dica? Agradecemos antecipadamente
WSK de
Você deve ter a fatura no estado persistente, não transitória. Isso significa que o id já deve estar atribuído a ele. Então salve Invoiceprimeiro, para obter o id e depois salvar InvoiceItem. Você também pode jogar em cascata.
Alex Gitelman
13

Aqui você usou valor nativo e atribuindo valor à chave primária, em que a chave primária nativa é gerada automaticamente.

Portanto, o problema está chegando.

Bibhav
fonte
1
Se você acredita que tem informações adicionais a oferecer para uma pergunta que já tem uma resposta aceita, forneça uma explicação mais substancial.
ChicagoRedSox
8

Isso existe na relação @ManyToOne. Resolvi esse problema usando apenas CascadeType.MERGE em vez de CascadeType.PERSIST ou CascadeType.ALL. Espero que ajude você.

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

Solução:

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

Provavelmente, o problema está fora do código que você está mostrando aqui. Você está tentando atualizar um objeto que não está associado à sessão atual. Se não é a Fatura, então talvez seja um InvoiceItem que já foi persistido, obtido do banco de dados, mantido ativo em algum tipo de sessão e então você tenta persistir em uma nova sessão. Isso não é possível. Como regra geral, nunca mantenha seus objetos persistentes ativos nas sessões.

A solução será obter todo o gráfico do objeto da mesma sessão com a qual você está tentando persistir. Em um ambiente da web, isso significaria:

  • Obtenha a sessão
  • Busque os objetos que você precisa para atualizar ou adicionar associações. De preferência por sua chave primária
  • Altere o que é necessário
  • Salve / atualize / remova / exclua o que quiser
  • Fechar / confirmar sua sessão / transação

Se você continuar tendo problemas, poste alguns dos códigos que estão chamando seu serviço.

Joostschouten
fonte
Obrigado @joostschouten. Aparentemente, não deve haver sessão aberta antes de chamar o método save, como mencionei em "Mais detalhes", que acrescentei no final da minha pergunta original. Existe alguma maneira de verificar se existe alguma sessão antes de chamar o método de salvamento?
WSK de
Sua suposição "Aparentemente, não deve haver uma sessão aberta antes de chamar o método de salvamento" está errada. No seu caso, você está envolvendo uma transação em torno de cada salvamento e obtenção, o que significa que as sessões abertas não devem ocorrer e, se ocorrerem, não serão úteis. Seu problema parece estar no código que manipula seu JSON. Aqui você passa uma nota fiscal com itens da nota fiscal que já existem (eles têm id's). Passe-o com ids nulos e provavelmente funcionará. Ou faça com que seu serviço de manipulação do JSON obtenha os itens da fatura do banco de dados, adicione-os à fatura e salve-os na mesma sessão em que você os obteve.
joostschouten
@joostschouten Agora estou recebendo este erro: "org.hibernate.PropertyValueException: a propriedade not-null faz referência a um valor nulo ou transitório: example.forms.InvoiceItem.invoice". Você poderia me dar uma ideia? Agradecemos antecipadamente
WSK de
1
Isso soa como uma nova pergunta para mim. Você não compartilhou conosco uma parte importante do código. O código que trata do JSON, gera seus Objetos de modelo e chama persiste e salva. Esta exceção informa que você está tentando persistir um invoiceItem com uma fatura nula. Que legitimamente não pode ser feito. Publique o código que realmente constrói seus objetos de modelo.
joostschouten
@joostschouten Isso faz sentido para mim, mas o problema é que estou usando uma estrutura "qooxdoo" para JSON e criando uma chamada RPC para o servidor onde tenho um utilitário de servidor RPC da mesma estrutura instalado. Então, tudo está embrulhado em classes de framework. Pode não ser prático extrair e postar milhares de linhas. Por outro lado, podemos observar o objeto "theInvoice" no lado do servidor que foi criado? ou mostrando informações de depuração / rastreamento de hibernação?
WSK de
2

Duas soluções 1. use merge se quiser atualizar o objeto 2. use save se quiser apenas salvar o novo objeto (certifique-se de que a identidade seja nula para permitir que o hibernate ou o banco de dados o gere) 3. se você estiver usando um mapeamento como
@OneToOne ( fetch = FetchType.EAGER, cascade = CascadeType.ALL) @JoinColumn (name = "stock_id")

Em seguida, use CascadeType.ALL para CascadeType.MERGE

obrigado Shahid Abbasi

Shahid Hussain Abbasi
fonte
0

Para JPA corrigido usando EntityManager merge () em vez de persist ()

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

Eu tive o "mesmo" problema porque estava escrevendo

@GeneratedValue(strategy = GenerationType.IDENTITY)

Excluí essa linha por não precisar dela no momento, estava testando com objetos e tal. Eu acho que é <generator class="native" />no seu caso

Não tenho nenhum controlador e minha API não está sendo acessada, é apenas para teste (no momento).

Miguel Avila
fonte