[Resolvido] Eu só quero uma forma elegante de capturar exceções do JPA vindas do contêiner

8 respostas
V

E até agora nada! Alguém pode me ajudar a capturar essas exceções? Num contexto web é tudo muito simples mas quando o negócio é EJB a viagem é grande demais. Pra ser mais específico do que estou tentando aqui: Estou gerando uma ConstrainException de propósito, adicionando duas pessoas com o mesmo CPF. Sei que a exceção(ões) (por ser RuntimeException) vem encapsulada numa EJBException. Até aí tudo bem. Mas como eu retiro a MySqlContrainException de dentro do EJBException? Talvez eu nem precise ir tão a fundo, basta pegar a PersistenceException mas o que quero mesmo é o detailMessage "Duplicated CPF “[telefone removido]” etc, pois é o que realmente importa para eu saber tratar e enviar para a view.

Alguém me ajuda?

8 Respostas

D

Ja tentou chamar o flush do EM? Eu tive esse problema recentemente, e a forma que eu encontrei de pegar a exception original foi realizando um flush antes de commitar a transação.

R

Por que você não manda essa mensagem direto no EJB?

Método do DAOpublic Entity meuMetodo(){ try{ // Implementação }catch(WhateverException e){ throw new ConstraintException("Duplicated CPF: [telefone removido]") } }Método do EJBpublic Entity meuMetodoEjb(){ try{ dao.meuMetodo(); }catch(ConstraintException e){ throw new EJBException(e.getMessage()); } }

V

Olá diegosammet, já estou usando o flush para sincronizar a transação. A exceção do banco de dados já está vindo do contêiner só que vem encapsulada num EjbException (EJBException > PersistenceException > DataBaseException > MySQLIntegrityConstraintViolationException) e a mensagem é “Duplicate entry ‘2895570’ for key ‘RG’”…

Vou tentar aqui navegar nessa árvore de Exceções para ver se alcanço a mensagem de MySQLIntegrityConstraintViolationException e depois relanço uma exceção personalizada da Aplicação… (Não sei se isso é tão elegante mas acho que deve ser a única solução viável)

Olá Rodrigo Sasaki, acho que não é uma boa fazer isso aí não pois assim estou praticamente determinando a característica do erro sem analisar a mensagem que vem do banco. Por exemplo, tenho “Duplicate entry ‘2895570’ for key ‘CPF’” mas também poderia ter “Duplicate entry ‘2895570’ for key ‘RG’” e lançar mensagens diferentes mais elaboradas. :wink:

R

Incrível…

Cara, eu só copiei a mensagem que você exemplificou, você coloca a mensagem que você preferir, o detalhe importante é saber como levar a mensagem até a view.

N

Também tentei alguma forma elegante de tratar uma exceção específica, a de unique constraint, porém, ainda não encontrei. O que eu estou fazendo atualmente é identificando em qual campo ocorreu através do getMessage() e aí exibo a mensagem personalizada.

public boolean save(Object entity) {
        try {
			if (((BaseEntity<?>)entity).isNew())
				em.persist(entity);
			else
				em.merge(entity);
			Util.facesMessage("Operação efetuada com sucesso");	
			return true;
        }catch (PersistenceException p){
        	if(p.getMessage().contains("ConstraintViolationException")){
        		if(p.getMessage().contains("uk_txtEmail"))
        			Util.facesError("Já existe um cadastro");
        		else if(p.getMessage().contains("uk_txtCnpj"))
        			Util.facesError("Já existe um cadastro");
        		else if(p.getMessage().contains("uk_..."))
        			Util.facesError("Já existe um cadastro");
        		else
        			Util.facesError("Registro com informações já existentes na base de dados");
        	}
        	else{
        		p.printStackTrace();
        		Util.facesError("Problemas ao gravar no banco");
        	}
		} catch (Exception e) {
			e.printStackTrace();
			Util.facesError("Inconsistências nos dados");
		}
        return false;
    }
D

Eu lembro de ter implementado algo do tipo, não sei se é a forma mais elegante de se fazer, ou nem mesmo se é elegante… Mas é uma opção:

public void create(T entity) throws NuloException, UniqueConstraintException{ 

 try{
            getEntityManager().persist(entity);
            }
            catch(PersistenceException e){
               if (e.getCause().getClass().equals(ConstraintViolationException.class)){
                   throw new UniqueConstraintException(e.getCause());
               }
            }
}
public class UniqueConstraintException extends Exception {

    /**
     * Creates a new instance of <code>UniqueConstraintException</code> without detail message.
     */
    private String mensagem;
    public UniqueConstraintException() {
    }

    public UniqueConstraintException(Throwable t) {
       mensagem = formataMensagem(t);
        
    }

    public UniqueConstraintException(String msg) {
        super(msg);
    }

    @Override
    public String getMessage() {
        return mensagem;
    }
    
    private String formataMensagem(Throwable t){
        int limit = t.getMessage().length();
        String coluna = t.getMessage();

        coluna = t.getMessage().substring(16, limit - 1);
        int finalValor = coluna.indexOf("for key");

        String valor = coluna.substring(0, finalValor);
        valor = valor.replace("'", " ");

        coluna = coluna.replaceFirst("_UNIQUE", " ");
        coluna = coluna.replaceAll("'", " ");
        coluna = coluna.replaceFirst("for key", " ");
        coluna = coluna.replaceFirst(valor, " ");
        coluna = coluna.trim();
        valor = valor.trim();
        
        String msg = "Não foi possivel inserir. Registro '"+ valor +"' duplicado para "
                + "a coluna '"+coluna+"'";
        return msg;
    }
}

E por final :

try
            {
                ejbFacade.create(entidade);
                
            } catch (UniqueConstraintException ex)
            {
                FacesUtil.addMessage(Mensagem.getError(ex.getMessage()));
                
            }
D

A diferença é que ai eu utilizava o Hibernate como implementação JPA, e ele trata as exceptions do container de uma forma diferente, então ele lançava um Persistence Exception. Mas de qualquer maneira, você pode descobrir a exception que causou a exception lançada pelo container com o getCause.getClass(); ou navegando pelo getStackeTrace();

V

Olá pessoal, a forma que encontrei foi criar uma classe utilitária para tratar as exceções.

Interface ExceptionsUtils

public interface ExceptionsUtils {
    

    /**
     * Varre a EJBException (ou qualquer Exception) em busca de suas
     * exceções internas aninhadas e retorna a String com a mensagem
     * proviniente da Exceção principal (a mensagem da exceção que realmente
     * foi a causa do erro).
     * @param e Uma exceção de Entrada, provavelmente EJBException.
     * @return Uma string com a verdadeira causa do erro vinda da exceção principal na árvore de exceções de EJBException.
     */
    public String getMensagemPrincipalDaExcecao(Exception e) throws Exception;
    
    /**
     * Varre a EJBException (ou qualquer Exception) em busca de suas
     * exceções internas aninhadas e retorna a Exceção principal
     * (a exceção que realmente foi a causa do erro).
     * @param e Uma exceção de Entrada, provavelmente EJBException.
     * @return Uma exceção da verdadeira causa do erro vinda da árvore de exceções de EJBException.
     */
    public Exception getExcecaoPrincipalDaExcecao(Exception e) throws Exception;
    
    /**
     * Analisa e manipula a exceção repassada ao método, depois prepara
     * uma exceção para o tipo repassado ao método de acordo com a mensagem
     * (causa da exception) principal.
     * @param e Exceção recebida para que haja a manipulação em cima dela.
     * @return Uma exceção do tipo repassado ao método.
     * @throws Exception Caso haja algo de errado com o método uma exceção será lançada.
     */
    public Exception getExcecaoPreparada(Exception e) throws Exception;
}

Classe TratadorDeExceçõesEJBUtil

public class TratadorDeExcecoesEJBUtil implements ExceptionsUtils {

    @Override
    public String getMensagemPrincipalDaExcecao(Exception ex) throws Exception {
        if (ex != null) {
            Exception atual = ex;
            atual = varrerArvoreDeExcecoes(atual);
            return atual.getMessage();
        } else {
            throw new Exception("O valor recebido para tratamento de exceção foi null");
        }
    }

    @Override
    public Exception getExcecaoPrincipalDaExcecao(Exception ex) throws Exception {
        if (ex != null) {
            Exception atual = ex;
            atual = varrerArvoreDeExcecoes(atual);
            return atual;
        } else {
            throw new Exception("O valor recebido para tratamento de exceção foi null");
        }
    }

    @Override
    public Exception getExcecaoPreparada(Exception e) throws Exception {
        String s = getMensagemPrincipalDaExcecao(e);
        ArrayList<String> valoresRecebidosMensagemPrincipalAnalisada = new ArrayList<>();
        String mensagemFormatada = null;
        //Tentativa de adição de valor duplicado.
        if (s.toLowerCase().contains("duplicate")) {
            int n = 0;
            Pattern p = Pattern.compile("'(.*?)'"); //Padrão da expressão regular que procura pelos valores dentro de aspas.
            Matcher m = p.matcher(s);
            while (m.find()) {                              //Faz a busca pelo padrão
                valoresRecebidosMensagemPrincipalAnalisada.add(m.group(1)); //Pega valor
            }
            String novaMensagem = "O valor %s já existe para o campo %s";
            Object[] sArray = valoresRecebidosMensagemPrincipalAnalisada.toArray(); //Passa valores para um array de objetos.
            mensagemFormatada = String.format(novaMensagem, sArray); //Formata a saída.
        }
        return new Exception(mensagemFormatada);
    }

    private Exception varrerArvoreDeExcecoes(Exception atual) {
        boolean continuaBuscandoExcecoes = true;
        while (continuaBuscandoExcecoes) {
            if (atual.getCause() != null) {
                atual = (Exception) atual.getCause();
            } else {
                continuaBuscandoExcecoes = false;
            }
        }
        return atual;
    }
}

Assim posso chamar no meu Service o método salvarPessoa por exemplo:

...
try {
            pessoaDAO.salvar(pessoa);
            pessoaDAO.flush();
        } catch (EJBException ex) {
            ExceptionsUtils exceptionsUtils = new TratadorDeExcecoesEJBUtil();
            PessoaException pe = null;
            try {
                pe = new PessoaException(exceptionsUtils.getExcecaoPreparada(ex).getMessage());
            } catch (Exception ex1) {
                Logger.getLogger(PessoaService.class.getName()).log(Level.SEVERE, null, ex1);
                pe = new PessoaException(ex1.getMessage());
            }
            //Se quiser coloca um JOptionPane para ver o resultado...
            throw pe;
        }

Bem, essa foi a maneira mais elegante que encontrei para capturar a real exceção de uma EJBException, nesse caso tratei uma ConstrainException vinda do banco… No método getExcecaoPreparada, vou selecionando e tratando as exceções a maneira que devo. Se alguém souber outro modo fica em aberto o tópico…
Espero que tenha ajudado, valeu! :smiley:

Criado 1 de novembro de 2012
Ultima resposta 5 de nov. de 2012
Respostas 8
Participantes 4