Processing of database exceptions

Asked

Viewed 1,051 times

1

How to handle database exceptions with JPA? And, how to use annotations instead of the Imessage interface, as below?

Example:

  1. My database returns error: "Violation Constraint uk_email";
  2. Pick up the error message Constraint (uk_email) and look in the same database or in another message corresponding to it.

My solution so far:

  • Interface

    package br.com.handlingjpa;
    
    /**
    * Interface usada para identificar qual é a classe de entidade que
    * representa a tabela de mensagens
    * 
    * @author Thiago Santos <[email protected]>
    * 
    */
    public interface IMessage {
    
        /**
         * Obt&eacute; o nome da constraint encontrada
         * 
         * @return A constraint encontrada
         */
        String getConstraint();
    
        /**
         * Defini qual foi a constraint encontrada na exce&ccedil;&atilde;
         * 
         * @param constraint O nome da constraint
         */
        void setConstraint(String constraint);
    
        /**
         * Obt&eacute; a mensagem referente a contraint
         * 
         * @return A mensagem
         */
        String getMessage();
    
        /**
         * Determina qual &eacute; a mensagem referente a constraint
         * 
         * @param message  A mensagem
         */
        void setMessage(String message);
    
        /**
         * Obt&eacute; a express&atilde;o regular que determina como encontrar uma
         * constraint
         * 
         * @return A express&atilde;o regular
         */
        String getRegex(); 
    
    }
    
  • Handling Exception class

    /*
     * Copyright (c) 2014. All rights reserved. 
     */
    package br.com.handlingjpa;
    
    import java.sql.SQLException;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    import javax.persistence.EntityManager;
    import javax.persistence.Query;
    
    /**
     * <p>
     * A classe <code>HandlingDBException</code> &eacute; utilizada para tratar
     * erros gerado pela base de dados. A mesma busca na exce&ccedil;&atilde;o (a
     * resposta dada pela base de dados do ocorrido) por um identificador do erro.
     * Ao encontrar-lo, a classe realiza uma consulta em busca da mensagem
     * correspondente.
     * </p>
     * 
     * @author Thiago Santos <[email protected]>
     * 
     */
    public class HandlingDBException {
    
        /**
         * A classe de entidade que faz refer&ecirc;ncia a tabela de mensagens
         */
        private IMessage entityClass;
        /**
         * O nome da <code>NamedQuery</code> usada para consulta
         */
        private String namedQuery;
        /**
         * O nome do par&acirc;metro na <code>NamedQuery</code> usado como filtro
         */
        private String paramName;
    
        /**
         * Cria um HandlingDBException e determina quem &eacute; a classe de
         * entidades, o nome da NamedQuery e o identificador do par&acirc;metro na
         * NamedQuery
         * 
         * @param entityClass A <b>inst&acirc;ncia</b> da classe de entidade. Ex: <code>new Entidade()</code>
         * @param namedQuery O nome da NamedQuery de consulta
         * @param paramName O identificador do par&acirc;metro na NamedQuery
         */
        public HandlingDBException(IMessage entityClass, String namedQuery,
                String paramName) {
            this.entityClass = entityClass;
            this.namedQuery = namedQuery;
            this.paramName = paramName;
        }
    
        /**
         * Busca uma mensagem com base em um erro enviado pela base de dados. Caso
         * encontre, ser&aacute; adicionado a entidade a constraint encontrada e a
         * mensagem correspondente a ela. Caso contr&aacute;rio, ser&aacute;
         * retornado a entidade do jeito que foi passada no construtor
         * 
         * @param exception Um Throwable com a exce&ccedil;&atilde;o
         * @param connection A conex&atilde;o que diz onde executar a consulta
         * @return Retorna a pr&oacute;pria entidade passada no construtor
         */
        public IMessage getMessageFromDatabase(Throwable exception,
                EntityManager connection) {
            // Busca por uma SQLException ou ate que seja null
            while (exception != null && !(exception instanceof SQLException)) {
                exception = exception.getCause();
            }
            // Verifica se e uma SQLException
            if (exception instanceof SQLException) {
                SQLException ex = (SQLException) exception;
                // Monta a regex de constraint
                Pattern pattern = Pattern.compile(entityClass.getRegex());
                // Procura a ocorrencia na mensagem de erro
                Matcher matcher = pattern.matcher(ex.getMessage());
                // Verifica se achou alguma constraint
                if (matcher.find()) {
                    // Obtem a constraint no meio da mensagem
                    String constraint = ex.getMessage().substring(matcher.start(), matcher.end());
                    entityClass.setConstraint(constraint);
                    // Busca a mensagem na base de dados
                    Query query = connection.createNamedQuery(namedQuery);
                    query.setParameter(paramName, constraint);
                    String message = (String) query.getSingleResult();
                    entityClass.setMessage(message);
                }
            }
            return entityClass;
        }
    }
    
  • Entity Class

    package br.com.handling.modelo;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.Id;
    import javax.persistence.NamedNativeQuery;
    import javax.persistence.NamedQuery;
    import javax.persistence.Table;
    
    import br.com.handlingjpa.IMessage;
    
    @Entity
    @Table(name = "tb_mensagem")
    @NamedNativeQuery(
                      name = "findMensagemFunction",
                      query = "SELECT dbo.uf_buscar_mensagem(:constraint)")
    @NamedQuery(
                name = "findMensagemQuery",
                query = "SELECT m.message FROM Mensagem m WHERE m.constraint = :constraint")
    public class Mensagem implements IMessage {
    
        @Id
        @Column(name = "tx_constraint")
        private String constraint;
    
        @Column(name = "tx_mensagem")
        private String message;
    
        public Mensagem() {
    
        }
    
        @Override
        public String getConstraint() {
            return this.constraint;
        }
    
        @Override
        public void setConstraint(String constraint) {
            this.constraint = constraint;
        }
    
        @Override
        public String getMessage() {
            return this.message;
        }
    
        @Override
        public void setMessage(String message) {
            this.message = message;
        }
    
        @Override
        public String getRegex() {
            return "(?i)([pfuc]k_\\w+)";
        }
    
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result
                    + ((constraint == null) ? 0 : constraint.hashCode());
            return result;
        }
    
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Mensagem) {
                Mensagem msg = (Mensagem) obj;
                if (msg.getConstraint() != null) {
                    return msg.getConstraint().equals(this.getConstraint());
                }
            }
            return false;
        }
    
        @Override
        public String toString() {
            return this.getMessage();
        }    
    }
    
  • Main class

    public static void main(String[] args) {
    
        EntityManager em = // A conexão
    
        Teste t = new Teste();
        t.setId(2);
        t.setEmail("[email protected]");
    
        try {
            em.getTransaction().begin();
            em.persist(t);
            em.getTransaction().commit();
        } catch (PersistenceException pex) {
            // Tratando a exceção e buscando a mensagem usando SELECT
            HandlingDBException hdbe = new HandlingDBException(new Mensagem(),
                    "findMensagemQuery", "constraint");
            System.out.println(">>>> Mensagem: "
                    + hdbe.getMessageFromDatabase(pex, em).getMessage());
            // Realizando log
            pex.printStackTrace();
        } finally {
            // Fechar conexão
        }
    
    }
    

More details are on my github.com/programmerthi.

  • I don’t know any kind of framework in that sense, @Ascension. I’ve seen exceptions treatment by message snippet as you want, but all "done in the nail" even, nothing ready.

  • @Luizfernandobueno appreciated the answer. The question is that I had to create the sources of the links above and it was complicated. If there was a framework it would make my :D side easier

1 answer

2

Restrictions added to the database should not be the primary application source for the validation rules, but only an additional guarantee.

Using JPA, you can use the Bean Validation API with annotations to validate individual fields. For example, a field NOT NULL takes the note @NotNull with the attribute message to specify the desired message. The message can even be internationalized in files .properties.

Another technique used is to create generic DAO classes that do validations if, for example, if the record already exists before the inclusion and if it does not exist before the change and deletion.

Anyway, from everything I’ve seen, the app is who should handle these cases.

Even when we talk of record dependency for deletion or even cascading deletion, use cases should provide for this type of possibility.

  • That’s right, friend, it is one of the alternatives and still has the possibility of internationalization. But this is an increase!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.