Estou com um problema que achei, inicialmente que podia ser do OC4J ou Toplink (Essentials), rodando com JPA, mas vi que com o Hibernate ocorre o mesmo.
Estou lendo um arquivo onde cada linha tem os dados de um novo registro a grava no BD. Devo processar esse arquivo e gravar linha a linha os dados no BD. Faço isso com JPA (oc4j + toplink). Se algum registro dr algum pau, eu pulo para o próximo e sigo até o final, confirmando a transação, ou seja, grava os OK e os com erro não gravam.
Acontece que em todos os cenários (dentro ou fora oc4j, com toplink ou hibernate), ele faz rollback da transação toda depois que persisto um registro com erro (ex: chave duplicada ou valor longo demais para o campo).
Não entendi porque no final ele não confirma a transação.
Exemplo do código:
[code]EntityManager em = getEntityManager();
// se rodo fora do oc4j, habilito a linha abaixo //em.getTransaction().begin();
for( String linha: arquivo ) {
try {
MeuObj obj = new MeuObj();
// seta os valores da linha nos atributos do obj
Erro com Hibernate: [quote]Exception in thread “main” javax.persistence.RollbackException: Transaction marked as rollbackOnly[/quote]
Erro com Toplink: [quote]Caused by: javax.persistence.RollbackException: Transaction rolled back because transaction was set to RollbackOnly[/quote]
Uso EJB 3.0 (dentro do oc4j) ou classe normal (fora). Nada de Spring!
Maracuja, nem testei, mas posso dizer que isso não funcionaria, ainda mais se eu levar isso pra dentro do app server, onde vou legar ao container o gerenciamento da transação.
Isso pode funcionar no EJB, mas fora dele ainda continuaria com erro.
E, para mim, não faz muito sentido ele dar rollback na transação toda, sendo que eu estou tratando um erro específico.
Imagina a seguinte transação:
INSERT
DELETE
UPDATE
INSERT
Imagina que o UPDATE não seja um passo que, obrigatoriamente, tenha que ser efetivado com sucesso, ou seja, mesmo que dê erro, as outras operações devem ser confirmadas na transação. Não tem cabimento ele dar rollback na minha transação sendo que eu trato um ponto específico.
Pois é, mas ate onde sei esse é o comportamento padrão. Tudo ou nada.
Por isso existemos diferentes tipos de isolamento.
Vc vai precisar fazer de um dos jeitos sugeridos:
1 - criar um metodo para salvar e fazer ele ser REQUIRED_NEW.
2- criar uma transacao para cada registro. Pode ser dentro do metodo save mesmo.
Não vejo outra opção.
[]´s
ps. se vc estivesse usando Spring seria bem facil de fazer REQUIRED_NEW.
Pois é, de acordo com o link, todas os EJBs por padrão são marcados como “Required” na transação. E a transação é demarcada pelo início e fim do método do bean.
Então, se eu faço assim:
public void meuMetodo() {
em.persist( x );
em.merge( y );
try {
em.delete( w );
} catch( Exception e ) {
}
em.persist( z );
}
Se der pau no delete, eu to dando um catch e mando seguir o resto. Ele deveria dar commit se o resto foi OK.
Eu acredito que ainda vai dar rollback, porque a exception está ocorrendo dentro da transação (que como você destacou, começa no inicio de seu método).
Teria que ter um jeito de isolar esse trecho
try {
em.delete( w );
} catch( Exception e ) {
}
em outro método.
Só que aí, talvez não atenda a seus requisitos de transação. Se der pau no ultimo persist "em.persist( z ); ", ele deve dar rollback em tudo, inclusive no “em.delete( w );”?
NESTE caso (o primeiro código) cada persist() é um registro gravado (transação atômica).
Mas no fictício caso acima (DELETE), tudo seria uma transação toda, onde apenas o DELETE seria opcional.
É, no caso dos insert a unica forma que vejo é usando o REQUIRES_NEW. Acho que tambem funciona fora do EJB, sendo que no seu método save vc teria algo do genero
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void save(MeuObj obj){
try{
EntityManager em = getEntityManager();
//em.getTransaction().begin(); //descomentado quando tiver fora do EJB
em.persist(obj);
em.flush();
//em.getTransaction().commit();
catch(Exception ex){
//tratar
//em.getTransaction().rollback();
}
}
o problema é aparentemente simples, mas da dor de cabeça neh? rs
Olha, um detalhe importante das sessions do Hibernate (que, vendo isso, eu suponho que seja verdade também pra os EntityManagers) é que se uma Session lançar uma exceção, ela deve ser descartada na mesma hora, ela não deve ser reutilizada pra nada.
Não lembro mais exatamente aonde eu li isso, mas eu suponho que tenha sido no bom e velho Hibernate in Action.
Resolvi e parece que entendi o problema todo. Vou explicar o meu caso aqui para vocês entenderem melhor.
Tenho um EJB Stateless Session Bean (ESSB) que serve como meu repositório de dados e trabalha internamente com o EntityManager do JPA.
Tenho outro ESSB que tem as regras de negócio. Um dos processos é ler um arquivo e gravar cada linha como um novo registro no BD. Se uma linha der erro, devo registrar um log e continuar a gravar os outros registros até o fim. Não posso simplesmente dar rollback. O controle transacional é feito pelo container (CMT).
while( (linha = lerLinha()) != null ) {
MeuObj o = new MeuObj( dadosDoArquivoAqui );
try {
ejbB.gravar( o );
} catch( Exception e ) {
// log do registro com problemas
}
}
Meu EJB_B estava mais ou menos assim:
public void gravar(T t) {
em.persist(t);
em.flush();
}
Acontece que qualquer exceção lançada pelo EntityManager (ex: chave duplicada), invalidava toda minha transação, mesmo eu tratando. Eu considerava que a transação seria confirmada (commit) se o método do EJB originalmente chamado retornasse com sucesso. Mas a exceção do EM invalida tudo (rollback).
[code]while( (linha = lerLinha()) != null ) {
MeuObj o = new MeuObj( dadosDoArquivoAqui );
try {
gravaComTransacaoNova( o );
} catch( Exception e ) {
// log do registro com problemas
}
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void gravaComTransacaoNova(MeuObj o) {
ejbB.gravar( o );
}[/code]
Mesmo assim não funcionou. Li no livro EJB in Action (versão pt_br) que o JPA não funciona com transação por método.
while( (linha = lerLinha()) != null ) {
MeuObj o = new MeuObj( dadosDoArquivoAqui );
try {
ejbB.gravar( o );
} catch( Exception e ) {
// log do registro com problemas
}
}
EJB_B
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void gravar(T t) {
em.persist(t);
em.flush();
}
Então funcionou, ou seja, ele cria uma nova transação para cada gravação e se der erro no EM, ele executa os próximo.
Conclusão, no mesmo EJB não rola definir uma nova transação no método. De EJB para outro EJB funciona.
Problema: Eu não deveria fazer isso, haja vista que o EJB_B é mais granular e reutilizado em outros serviços do sistema. Eu queria ser capaz de fazer isso no meu EJB_A (de negócio), que é mais específico.