Erro JPA - deleted entity passed to persist [RESOLVIDO]

Boa tarde pessoal.

Estou com problema ao utilizar JPA para o seguinte caso.
Tenho quatro tabelas relacionadas, todas um para muitos (1ª um para muitos 2ª, 2ª um para muitos 3ª, etc). Em determinado momento tenho que atualizar um registro da 1ª tabela e deletar os registros referentes a este primeiro nas demais tabelas.

O mapeamento entre elas está assim:

1ª tabela:

...
@Id
@Basic(optional = false)
@Column(name = "ID_SAIDA", nullable = false)
@GeneratedValue(generator = "saida", strategy = GenerationType.SEQUENCE)
@SequenceGenerator(name = "saida", sequenceName = "CLT.AA_SAIDA_COO_S", initialValue = 1, allocationSize = 1)
private Long idSaida;
...
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idSaida")
private List<SaidaLote> saidaLoteList;
...

2ª tabela:

...
@Id
@Basic(optional = false)
@Column(name = "ID_SAIDALOTE", nullable = false)
@GeneratedValue(generator = "saidalote", strategy = GenerationType.SEQUENCE)
@SequenceGenerator(name = "saidalote", sequenceName = "CLT.AA_SAIDALOTE_COO_S", initialValue = 1, allocationSize = 1)
private Long idSaidaLote;
...
//referente à 3ª tabela
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idSaidalote")
private List<SaidaPega> saidaPegaList;

//referente à 1ª tabela
@JoinColumn(name = "ID_SAIDA", referencedColumnName = "ID_SAIDA", nullable = false)
@ManyToOne(optional = false)
private Saida idSaida;
...

O código que executa a tarefa que eu disse acima é este:

//em caso de erro
em.getTransaction().begin();
saida.setInSituacao("P");
saidaDao.update(saida);
saidaDao.commit();

em.getTransaction().begin();
Dao<SaidaLote> saidaLoteDao = new Dao<SaidaLote>(SaidaLote.class, em);
saidaLoteDao.load(saidaLote, saidaLote.getIdSaidaLote());
saidaLoteDao.delete(saidaLote);
saidaLoteDao.commit(); //linha que dá o erro

E o erro gerado por esse código:

javax.persistence.RollbackException: Error while commiting the transaction
        at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:71)
        at br.com.cooxupe.armazem.bd.Dao.commit(Dao.java:41)
        at br.com.cooxupe.armazem.webservice.SaidaImpl.salvarSaida(SaidaImpl.java:453)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.codehaus.xfire.service.invoker.AbstractInvoker.invoke(AbstractInvoker.java:59)
        at org.codehaus.xfire.service.invoker.ObjectInvoker.invoke(ObjectInvoker.java:45)
        at org.codehaus.xfire.service.binding.ServiceInvocationHandler.sendMessage(ServiceInvocationHandler.java:320)
        at org.codehaus.xfire.service.binding.ServiceInvocationHandler$1.run(ServiceInvocationHandler.java:86)
        at org.codehaus.xfire.service.binding.ServiceInvocationHandler.execute(ServiceInvocationHandler.java:134)
        at org.codehaus.xfire.service.binding.ServiceInvocationHandler.invoke(ServiceInvocationHandler.java:109)
        at org.codehaus.xfire.handler.HandlerPipeline.invoke(HandlerPipeline.java:131)
        at org.codehaus.xfire.transport.DefaultEndpoint.onReceive(DefaultEndpoint.java:64)
        at org.codehaus.xfire.transport.AbstractChannel.receive(AbstractChannel.java:38)
        at org.codehaus.xfire.transport.http.XFireServletController.invoke(XFireServletController.java:304)
        at org.codehaus.xfire.transport.http.XFireServletController.doService(XFireServletController.java:129)
        at org.codehaus.xfire.transport.http.XFireServlet.doPost(XFireServlet.java:116)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
        at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:849)
        at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
        at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:454)
        at java.lang.Thread.run(Thread.java:619)
Caused by: org.hibernate.ObjectDeletedException: deleted entity passed to persist: [br.com.cooxupe.pojo.SaidaLote#<null>]
        at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:90)
        at org.hibernate.impl.SessionImpl.firePersistOnFlush(SessionImpl.java:644)
        at org.hibernate.impl.SessionImpl.persistOnFlush(SessionImpl.java:636)
        at org.hibernate.engine.CascadingAction$9.cascade(CascadingAction.java:323)
        at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:268)
        at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:216)
        at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
        at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:296)
        at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:242)
        at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:219)
        at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
        at org.hibernate.engine.Cascade.cascade(Cascade.java:130)
        at org.hibernate.event.def.AbstractFlushingEventListener.cascadeOnFlush(AbstractFlushingEventListener.java:131)
        at org.hibernate.event.def.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:122)
        at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:65)
        at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:26)
        at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
        at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
        at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
        at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:54)
        ... 32 more

Já procurei em vários lugares mas não obtive sucesso. Espero que alguém aqui possa me ajudar. Fico no aguardo.

Muito Obrigado.

Meu grande conterraneo RafDutra!

Vejo que estas com problemas ai em tal empresa cafeeira! eheheh…

Cara, primeiro, cuidado com essa propriedade na hora de declarar seus Sequences (“allocationSize = 1”). Muitos pensam que essa propriedade serve para incrementar em 1 o seu valor da sequence. Na verdade, isso é o tamanho do buffer que será armazenado antes que o Hibernate (JPA) busque novamente o proximo valor da sequence no banco. Nao sei se fui claro …

Segundo… o que percebo é que a classe Saida tem uma lista de SaidaLote, correto?

Esse segundo trecho de codigo que voce passou por acaso tem um objeto saidaLote que pertence a lista contida no objeto saida, que foi recentemente atualizado no primeiro trecho de codigo?

O que me parece é o seguinte:

Nesse trecho de codigo vc tem um objeto da classe Saida que foi atualizado:

 //em caso de erro  
 em.getTransaction().begin();  
 saida.setInSituacao("P");  
 saidaDao.update(saida);  
 saidaDao.commit();  

Nesse segundo trecho de codigo vc tem um objeto da classe SaidaLote que pertence a um objeto da classe Saida, que (se for como eu penso) foi atualizado no primeiro trecho de codigo:

 em.getTransaction().begin();  
 Dao<SaidaLote> saidaLoteDao = new Dao<SaidaLote>(SaidaLote.class, em);  
 saidaLoteDao.load(saidaLote, saidaLote.getIdSaidaLote());  
 saidaLoteDao.delete(saidaLote);  
 saidaLoteDao.commit(); //linha que dá o erro  

Como o Saida foi atualizado no trecho 1, e voce nao deu um FLUSH no EntityManager, o SaidaLote no segundo trecho de codigo pertence a um Saida que não mais existe, visto que foi atualizado.

Voce pode tentar dar um flush no EM ao final do primeiro trecho de codigo para que as alterações sejam persistidas ao banco, assim vc buscaria um objeto no banco que já existiria, e o deletaria. Entendeu?

Espero ter ajudado, to na correria e nao analizei muito bem.

Se nao funcionar poste novamente que analizarei com mais calma em casa depois.

[]s
Iscariotes

e poste tb da onde vem o objeto saida criado no trecho 1 , e saidaLote no trecho 2.

Grande amigo Iscariotes.
Você não vai acreditar, mas sofrendo com esse negócio aqui o dia todo pensei, “se o Pedro tivesse aqui ele ia saber isso”. Hahahah

Seguindo o seu raciocínio, o objeto saidaLote realmente pertence ao objeto saida em um relacionamento 1-N. O objeto saidaLote encontra-se em um List.
No momento em que insiro esses objetos eu insiro UM objeto saida e UM objeto saidaLote, mesmo sendo 1-N. Caso ao inserir um segundo registro eu apenas recupero o objeto saida e adiciono um objeto saidaLote.

Esse trecho de código que postei deve acontecer quando ocorre um erro de um outro método. Pra exemplificar vai o código:


//CRIO O OBJETO SAIDA E O PREENCHO COM OS DADOS
//PESQUISO O OBJETO NO BANCO POR DOIS ATRIBUTOS
//CASO EXISTA PEGA A REFERENCIA, CASO CONTRÁRIO PEGO A REFERENCIA DO OBJETO VAZIO
...
//CRIO O OBJETO SAIDALOTE E O PREENCHO COM OS DADOS
//ASSOCIO O OBJETO SAIDALOTE AO OBJETO SAIDA (LIST)
...
//CRIO O OBJETO DA TERCEIRA TABELA E PREENCHO COM OS DADOS
//ASSOCIO O OBJETO DA TERCEIRA TABELA AO OBJETO SAIDALOTE (LIST)
...
//CRIO OS OBJETOS DA QUARTA TABELA E OS PREENCHO
//ASSOCIO OS OBJETOS DA QUARTA TABELA COM O OBJETO DA TERCEIRA TABELA (LIST)

            try {
                saidaDao.create(saida);   //método persist do JPA (Todos os mapeamentos estão com cascadeType.ALL)
                saidaDao.commit();         

                //caso método atualizaSaidaERP retorne erro tenho que apagar os dados da tabela SaidaLote (Linha) em diante
                //e atualizar o registro da tabela Saida (Cabeçalho)
                    msg = atualizaSaidaERP(saidaLote.getIdSaidaLote(), saida.getCdOc(), saida.getCdPlanta(), saida.getCdOrganizacao(), saida.getCdOrdemVenda(), saida.getDsPlaca(), saidaLote.getCdColetor(), saida.getCreatedBy());
                    if (msg != null) {
                        em.getTransaction().begin();
                        saida.setInSituacao("P");
                        saidaDao.update(saida);
                        saidaDao.commit();               //até aqui funciona - o registro é atualizado


                        em.getTransaction().begin();
                        em.flush();                           //Coloquei a linha que você disse mas continua o erro

                        SaidaLoteDao saidaLoteDao = new SaidaLoteDao(em);
                        saidaLote = saidaLoteDao.findById(saidaLote.getIdSaidaLote()); //tentei buscar o objeto através do seu ID (gerado  pela sequence)
                        saidaLoteDao.delete(saidaLote);
                        saidaLoteDao.commit();        //ERROOOOOO
                    }
                em.close();

E quanto a sequence, qual a melhor estratégia para preencher o allocationSize??

Cara, brigadão pela força. Qualquer hora manda um daqueles emails…hahahahah!

Nobre Rãfãel!

Analisando com mais calma observei o seguinte:

1- Você tem seu EntityManager criado, ok?

2- Na linha 15 voce esta passando o objeto saida para ser persistido, deixando assim o objeto associado (atached) ao seu EntityManager.
Para conferir que seu objeto saida está atachado ao EntityManager use o seguinte metodo:

em.contains(saida);

Esse metodo vai retornar true se o objeto for gerenciado.

3- Na linha 24 voce esta alterando o objeto saida.

4- Na linha 32 voce está buscando o objeto saidaLote passando o id do mesmo. Voce nem precisava fazer isso pois o seu objeto saidaLote ainda está atachado ao EntityManager.
Confira

em.contains(saidaLote);

5- Na linha 33 voce está deletando um objeto que havia sido passado quando o EntityManager foi criado (que seja por cascade policies), e esse objeto ainda está sendo gerenciado por ele o que causa o erro.

Faça o seguinte:

em.getTransaction().begin();  
saida.setInSituacao("P");  
saidaDao.update(saida);  
em.flush();
saidaDao.commit();  

em.clear();

Esse metodo clear() desatacha todos os objetos gerenciados pelo EntityManager.
Uma dica: sempre antes de usar o metodo clear utilize o flush para que suas atualizações sejam realmente persistidas no banco.
Pois o commit por si só apenas comita a transação, mas nao diz em que momento elas serão propagadas para o banco. Quem diz isso é o FlushMode.

Confira como o objeto saida assim como o saidaLote estão desatachados nesse momento.

em.contains(saida);
em.contains(saidaLote);

Agora sim faz sentido voce utilizar o metodo abaixo:

saidaLote = saidaLoteDao.findById(saidaLote.getIdSaidaLote()); 

pois você está novamente atachando o objeto saidaLote ao EM.

Confira novamente:

em.contains(saidaLote);

Agora com seu objeto saidaLote novamente atachado, porem com o saida desatachado voce pode chamar o seu metodo delete que não terá erro.

Espero ter ajudado !!

E poste aqui se realmente funcionou pra ficar registrado, vai que mais alguem tem essa dúvida.

Faaaaaala Pedro.

Cara, totalmente resolvido. Deu certo e exatamente do jeito que eu precisava.

Realmente o problema estava no estado em que se encontrada os meus objetos. Outra coisa que me ajudou bastante (que eu não conhecia) é a possibilidade de visualizar o estado dos objetos através do método:

entityManager.contains(objeto);

A saída do programa ficou assim:

saidaDao.create(saida);
saidaDao.commit();
System.out.println("1> " + em.contains(saida));
System.out.println("2> " + em.contains(saidaLote));


1> True
2> True

em.getTransaction().begin();
saida.setInSituacao("P");
saidaDao.update(saida);
em.flush();
saidaDao.commit();

System.out.println("3> " + em.contains(saida));
System.out.println("4> " + em.contains(saidaLote));
em.clear();
System.out.println("5> " + em.contains(saida));
System.out.println("6> " + em.contains(saidaLote));


3> true
4> true
5> false
6> false

em.getTransaction().begin();
SaidaLoteDao saidaLoteDao = new SaidaLoteDao(em);
saidaLote = saidaLoteDao.findById(saidaLote.getIdSaidaLote());

System.out.println("7> " + em.contains(saida));
System.out.println("8> " + em.contains(saidaLote));

saidaLoteDao.delete(saidaLote);
saidaLoteDao.commit();


7> false
8> true

Problema resolvido e mensagem “deleted entity passed to persist” não aparece mais.

Valeu pela ajuda cara. Agora outra dúvida que ficou é em relação ao seu primeiro post que você disse sobre o “allocationSize”:

@GeneratedValue(generator = "saida", strategy = GenerationType.SEQUENCE)
@SequenceGenerator(name = "saida", sequenceName = "CLT.AA_SAIDA_COO_S", initialValue = 1, allocationSize = 1)    

Qual a melhor estratégia que eu posso adotar para esses casos?

Valeu Pedro, muito obrigado!

Madrugou ai nobre Rãfãel!

Entao, sobre o allocationSize é o seguinte. Se voce colocar igual a 1 você forçara o Hibernate (JPA) a buscar toda hora que precisar de um novo id o valor no banco. E essa busca é synchronized, ou seja, se você tiver varios caras buscando um registro de cada vez, todos ao mesmo tempo sua performance vai la embaixo, inclusive pode chegar a capotar seu servidor de aplicação (como ja ocorreu aqui).

Entao, ou deixe sem especificar esse valor, assim o hibernate assume o valor default, que se não me engano é 50, ou entao usa NativeGenerator que não é synchronized.

So que se voce colocar 50, voce terá 50 valores de sequence “em memoria”. Ai suponha que voce só usou os 2 primeiros valores dessa sequence e capotou seu server. Os outros 48 registros serão perdidos e você ficará com um “buraco” nos ids do seu banco. (Pelo menos o que entendi da especificação foi isso, se estiver errado alguem me corriga por favor). Caso contrario, a busca pela próxima faixa de valores da sequence será feita quando os 50 valores estiverem esgotados.

Entendeu?

E no mais, como andam as coisas ai? mande-me um email ou MP.

[]s

Fala Pedro.

Entendido o recado. Realmente não conhecia sobre o allocationSize do sequenceGenerator, e sim, pensei que essa propriedade seria para incrementar o valor da sequence.

Muito obrigado pela força cara.

Obs: MP enviada!