[RESOLVIDO] Erro de inserção no Hibernate

7 respostas
Tchello

Bom dia galera.

Estou passando por um problema usando JPA (através do Hibernate) .
Procurei por aí mas não achei nada que pudesse me ajudar, uma vez que é difícil encontrar tags para esse prob.
Enfim, esclarecerei o cenário antes de expor o problema.

Supondo que eu tenha as seguintes entidades(ps: todas são serializaveis):

public class A {

 @Id
 @Column(name = "id_a");
 private long codigo;

@ManyToMany
 @JoinTable(name = "A_B, joinColumns = @JoinColumn(name = "id_a"), inverseJoinColumns = @JoinColumn(name = "id_b"))
 private Set<B> bs;
 ...

}


 public class B{

 @Id
 @Column(name="id_b"
 private long codigo;

 ....
}

OK, beleza.
Se eu fizer a persistencia de A que contenha uma coleção de B funciona perfeitamente.

PORRÉÉÉÉÉÉMM cai em uma situação onde o analista de banco decidiu que eu teria uma outra entidade C que possui uma chave composta para a entidade de relacionamento das entidades acima, a A_B.
Como estou usando JPA fui obrigado a mapear também essa entidade de relacionamento, ficando assim:

public class A_B{

 @Id
 @ManyToOne
 @JoinColumn(name = "id_a")
 private A a;

 @Id
 @ManyToOne
 @JoinColumn(name = "id_b")
 private B b;
}


 public class C{
 
 @Id
 @Column(ame="id_c")
 private long codigo;

 @ManyToOne
 @JoinColumns( {
			@JoinColumn(name = "id_a", referencedColumnName = "id_a"),
			@JoinColumn(name = "id_b", referencedColumnName = "id_b") })
 private A_B ab;

}

Bom, esse mapeamento funcionou também nos cenários:

1- Quando eu faço a persistencia de A e B ele persiste perfeitamente.
2- Quando persisto C onde o relacionamento A e B já exista, ele também funciona

PORÉM quando persisto no mesmo método os registros de A e B, criando automaticamente o registro na tabela de relacionamento A_B e vou persistir C da sempre um erro dizendo que não existe o relacionamento A_B, porém acabei de cria-lo logo acima no mesmo método.

Compreendem?
Agora se eu fizer essa persistência em chamadas separadas, tudo funciona perfeitamente!

Lembrando que estou usando Hibernate 3.5, Glassfish V3, EJB 3.x, JDK 1.6_u20, Fedora… e postgres 8.4.

Já tentei usar o em.flush() logo depois de criar o relacionamento A_B e antes de tentar persistir C. (‘em’ é meu entitymanager injetado pelo conteiner jee no meu stateless session bean);

O que to entendendo é que quando crio o relacionamento A_B ele fica no cache do hibernate (na minha transação) e ao tentar finalizar o método ele executa as querys de persistencia de C mas que dependem de A_B, daí a dita Exception.

Não questionem os relacionamentos, foi uma decisão dos analistas de banco e faz todo o sentido no contexto em que estamos inseridos. Preferi não postar o código por questões de privacidade da empresa, não seria ético exibi-los por aqui (não nesse contexto).

Help?

Obrigado pessoal!

7 Respostas

Tchello

Então galera, tava dando uma olhada nas exceptions que isso ta me mandando e notei o seguinte trexo ANTES da mensagem que reclama que o registro da entidade A_B não existe:

[#|2010-08-16T14:35:03.451-0300|SEVERE|glassfish3.0.1|javax.enterprise.system.std.com.sun.enterprise.v3.services.impl|_ThreadID=32;_ThreadName=p: thread-pool-1; w: 3;|17314637 [p: thread-pool-1; w: 3] ERROR org.hibernate.event.def.AbstractFlushingEventListener - Could not synchronize database state with session

Por que isso? Será que é isso que ta dando o erro de que o registro não existe? Mas ainda assim não tenho idéia do que possa estar gerando essa exception.

Abraços!

E

num sei se já tentou isso, mas ao terminar de inserir o relacionamento, tenta dar um commit.

E verifica se as chamadas ao banco, quando na mesma camada, não ocorrem em threads diferentes. Se ocorrem em threads diferentes pode ser que o do relacionamento em C possa estar pedindo por um registro que ainda não foi “commitado” em A_B

Tchello

evefuji:
num sei se já tentou isso, mas ao terminar de inserir o relacionamento, tenta dar um commit.

E verifica se as chamadas ao banco, quando na mesma camada, não ocorrem em threads diferentes. Se ocorrem em threads diferentes pode ser que o do relacionamento em C possa estar pedindo por um registro que ainda não foi “commitado” em A_B

Então, mas preu conseguir dar um commit, preciso fazer o seguinte:

em.getTransaction().commit();

Fiz isso já e não funcionou, além de que se eu der o getTransaction() ele lança uma IllegalArgumentException dizendo que em transações gerenciadas pelo conteiner eu não posso mexer na transação.

E como seriam essas Threads diferentes?
Eu persisto o relacionamento A_B antes de C dentro do mesmo método e pelo mesmo EntityManager.
Realmente, a impressão que tenho é que ele tenta executar os inserts de C antes de A_B.
O curioso é que no console ele exibe os inserts na ordem certinha…

Enfim, estou ficando sem esperanças…

Obrigado!

Andre_Brito

Tchello,
Só por desencargo, se você atualizar A_B e tentar inserir C apontando para a relação atualizada, acontece a mesma coisa (acho que não vai lançar Exception, mas C vai ficar obsoleto)?

Outra coisa, você lida com alguma coisa de cache dentro do seu persistence.xml? Não me lembro no Hibernate, mas sei que no Eclipselink tem algumas configurações de cache.

Lembrei de alguma coisa que tinha lido na documentação do Hibernate:


3.10.1. In a transaction

Flush occurs by default (this is Hibernate specific and not defined by the specification) at the following points:

  • before query execution (*)
  • from javax.persistence.EntityTransaction.commit() (*)
  • when EntityManager.flush() is called ()
    (
    )* if a transaction is active

The SQL statements are issued in the following order

  • all entity insertions, in the same order the corresponding objects were saved using EntityManager.persist()
  • all entity updates
  • all collection deletions
  • all collection element deletions, updates and insertions
  • all collection insertions
  • all entity deletions, in the same order the corresponding objects were deleted using EntityManager.remove()

Referência.

Será que pode ter alguma relação com o seu problema?

Aliás, só mais uma coisa. Você falou que já tentou usar o flush e não deu certo, né? E tentou usar o refresh, mandando A_B?

Tchello

Andre Brito:
Tchello,
Só por desencargo, se você atualizar A_B e tentar inserir C apontando para a relação atualizada, acontece a mesma coisa (acho que não vai lançar Exception, mas C vai ficar obsoleto)?

Então, não entendi muito bem o que vc quis dizer, mas vamos la.
Fiz o seguinte:

//1 - Persistir A com relacionamento A_B;
 A a = new A();

 //inseri varios elementos ja cadastrados de B, na coleção de Bs de A;
 //logico que nao foi dando new assim a torto e direito, mas pulei a parte onde a coleção eh montada
 a.getBs().add(new B());
 a.getBs().add(new B());
 a.getBs().add(new B());

 //persisti a e dei um flush pra ele mandar bala nos inserts
 em.persist(a);
 em.flush();
 //nesse momento pude ver todos os inserts na entidade A e no relacionamento A_B pelo console
 
 //Logo depois recupero com JPQL a entidade de relacionamento A_B que desejo (eh uma das inseridas acima):
 String query = "...";
 A_B ab = (A_B)em.createQuery(query).getSingleResult();
 //A entidade A_B eh recuperada perfeitamente (o que não funciona se eu não der o flush la de cima) e eu sou capaz de confirmar que é de um dos inserts acima


 //crio a minha instância de C
 C c = new C();
 c.setAb(ab);

 //agora eu tento persistir C;
 em.persist(c);

 //ele até passa daqui, mas quando dou um flush manual ou o método termina é lançado o problema que descrevi.

Bom, esse trecho descreve muito bem o cenário que tenho.
Uma observação relevante a ser feita é que se eu executar isso em dois passos (duas chamadas distintas, não encadeadas) onde é feita a persistencia em separado tudo funciona perfeitamente.

Então, somente o padrão do hibernate.
Tentei mudar alguns argumentos mas sem sucesso.
Amanhã logo cedo postarei meus argumentos aqui.

Andre Brito:

Lembrei de alguma coisa que tinha lido na documentação do Hibernate:

Essa informação é valiosíssima!
Seguindo esse raciocínio, se bem o entendi, meu problema está sendo causado por que a persistência de A_B está sendo feita após o C, uma vez que envolve collections, que por essa lógica é executado apenas após entidades comuns. Seria isso?
Se for, como posso garantir que o passo 1 seja executado antes?

Andre Brito:

Aliás, só mais uma coisa. Você falou que já tentou usar o flush e não deu certo, né? E tentou usar o refresh, mandando A_B?

Na verdade não tentei. Um EntityManager regular possui esse método de refresh? (tem sim, encontrei no javadoc).
Lembrando que estou usando um EntityManager padrão do JPA injetado pelo conteiner, não o HibernateEntityManager, uma vez que não posso (nem tenho permissão) de atrelar esse código ao Hibernate (já que é desejo do cliente alternar para eclipselink, por exemplo, quando lhe for conveniente).

Pelo que li sobre o refresh, ele vai atualizar com o que está na base. O inverso também é valido, ele atualizará a base conforme a entidade caso esta não tenha sido persistida? E o controle de transações, não ficará afetado?

Amanhã assim que possível farei os testes com essa técnica.
Posto os resultados por aqui.

De qualquer maneira até lá qualquer ajuda, dica ou palpite pode ser de grande ajuda, já que acelera os testes que poderei executar.

Bom, creio que já tenhamos avançado bastante no modelo do cenário em que estou passando.
Agradeço imensamente a atenção dada ao meu tópico.

Abraços.

Tchello

Bom dia!

Conforme prometido, seguem os argumentos:

<property name="hibernate.hbm2ddl.auto" value="update" />
			<property name="hibernate.archive.autodetection" value="class, hbm" />
			<property name="hibernate.show_sql" value="true" />
			<property name="hibernate.format_sql" value="true" />
			<property name="use_sql_comments" value="true" />
			<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
			<property name="hibernate.transaction.flush_before_completion"
				value="true" />
			<property name="hibernate.transaction.auto_close_session"
				value="true" />
			<property name="hibernate.connection.release_mode" value="auto" />
			<property name="hibernate.current_session_context_class"
				value="jta" />

Já tentei altera-los de tudo quanto é maneira, mas infelizmente sem sucesso.

Testei o refresh, tanto após a inserção de A, quando em A_B após a sua recuperação.
O erro persiste =/ ou seja, ele não persiste (entendeu a piada? hehehehehehehe).

Queria uma forma de garantir que o passo 1 fosse executado no banco antes dos passos seguintes, pelo visto é isso que está acontecendo (o inverso) =/

Aguardo ansiosamente por respostas.

Abraços!

Tchello

Caros, encontrei a razão do problema.
No mapeamento com duas JoinColumns o hibernate estava gerando as FKs invertidas.
Vou explicar.

O correto seria gerar algo assim:

CONSTRAINT fkc03da487fe926996 FOREIGN KEY (id_a, id_b) REFERENCES A_B (id_a, id_b)

Mas por algum motivo ele estava gerando assim:

CONSTRAINT fkc03da487fe926996 FOREIGN KEY (id_a, id_b) REFERENCES A_B (id_b, id_a)

De qualquer maneira isso não mostrará um problema em produção, uma vez que jamais permitimos geração automática do hibernate nesse ambiente.
Portando a solução foi gerar esse relacionamento manualmente, fazendo tudo funcionar perfeitamente.

Analisarei melhor o caso, ao que tudo indica o relacionamento foi mapeado corretamente, porém o hibernate fez a geração erroneamente. Caso isso se confirme abrirei um issue no Hibernate e posto o código aqui.

Agradeço imensamente a atenção de todos e espero que isso possa contribuir para outros usuários no futuro.

Abraços!

Criado 16 de agosto de 2010
Ultima resposta 17 de ago. de 2010
Respostas 7
Participantes 3