Handling Database Exceptions

1

How to handle database exceptions with JPA? And, how to use annotations in place of the IMessage interface, as below?

Example:

  • My database returns the error: "Violation constraint uk_email";
  • Get the constraint of the error message (uk_email) and search the same or another database for a 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/programadorthi.

        
    asked by anonymous 04.07.2014 / 03:52

    1 answer

    2

    Restrictions added to the database should not be the primary source of the application for 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 NOT NULL field receives the annotation @NotNull . with the message attribute to specify the desired message. The message can even be internationalized in .properties files.

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

    Finally, from everything I've seen, the application is the one who should treat these cases.

    Even when we talk about the dependency of records for exclusion or even cascading, use cases should provide for this kind of possibility.

        
    04.07.2014 / 17:59