unidades:07_arquitectura:02_excepciones
Diferencias
Muestra las diferencias entre dos versiones de la página.
| — | unidades:07_arquitectura:02_excepciones [2023/04/07 21:26] (actual) – creado - editor externo 127.0.0.1 | ||
|---|---|---|---|
| Línea 1: | Línea 1: | ||
| + | ====== Excepciones ====== | ||
| + | Nuestro siguiente tema a tratar con la arquitectura de Hibernate es el tratamiento de las excepciones.Antes de ver este tema es recomendable la lectura de [[patrones: | ||
| + | ===== Tratamiento de Excepciones ===== | ||
| + | Al realizar una operación con Hibernate se pueden lanzar cualquiera de las siguiente 4 excepciones: | ||
| + | * <javadoc jee6> | ||
| + | * <javadoc h41> | ||
| + | * <javadoc jdk7> | ||
| + | * <javadoc jdk7> | ||
| + | Hemos indicado únicamente estas 4 excepciones ya que vamos a tratar cada una de ellas de forma distinta , no siendo necesario tratar ninguna otra ya que siempre caerán dentro de alguna de las cuatro. | ||
| + | |||
| + | Veamos cómo vamos a tratarlas. El siguiente código dentro del '' | ||
| + | <code java 1> | ||
| + | try { | ||
| + | session.beginTransaction(); | ||
| + | session.saveOrUpdate(entity); | ||
| + | session.getTransaction().commit(); | ||
| + | } catch (javax.validation.ConstraintViolationException cve) { | ||
| + | try { | ||
| + | if (session.getTransaction().isActive()) { | ||
| + | session.getTransaction().rollback(); | ||
| + | } | ||
| + | } catch (Exception exc) { | ||
| + | LOGGER.log(Level.WARNING," | ||
| + | } | ||
| + | throw new BussinessException(cve); | ||
| + | } catch (org.hibernate.exception.ConstraintViolationException cve) { | ||
| + | try { | ||
| + | if (session.getTransaction().isActive()) { | ||
| + | session.getTransaction().rollback(); | ||
| + | } | ||
| + | } catch (Exception exc) { | ||
| + | LOGGER.log(Level.WARNING," | ||
| + | } | ||
| + | throw new BussinessException(cve); | ||
| + | } catch (BussinessException ex) { | ||
| + | try { | ||
| + | if (session.getTransaction().isActive()) { | ||
| + | session.getTransaction().rollback(); | ||
| + | } | ||
| + | } catch (Exception exc) { | ||
| + | LOGGER.log(Level.WARNING," | ||
| + | } | ||
| + | throw ex; | ||
| + | } catch (RuntimeException ex) { | ||
| + | try { | ||
| + | if (session.getTransaction().isActive()) { | ||
| + | session.getTransaction().rollback(); | ||
| + | } | ||
| + | } catch (Exception exc) { | ||
| + | LOGGER.log(Level.WARNING," | ||
| + | } | ||
| + | throw ex; | ||
| + | } catch (Exception ex) { | ||
| + | try { | ||
| + | if (session.getTransaction().isActive()) { | ||
| + | session.getTransaction().rollback(); | ||
| + | } | ||
| + | } catch (Exception exc) { | ||
| + | LOGGER.log(Level.WARNING," | ||
| + | } | ||
| + | throw new RuntimeException(ex); | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Lo que debemos hacer siempre en las 4 excepciones es ver si hay una transacción activa y en ese caso hacer un '' | ||
| + | |||
| + | <code java> | ||
| + | try { | ||
| + | if (session.getTransaction().isActive()) { | ||
| + | session.getTransaction().rollback(); | ||
| + | } | ||
| + | } catch (Exception exc) { | ||
| + | LOGGER.log(Level.WARNING," | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | La propiedad estática '' | ||
| + | <code java> | ||
| + | private final static Logger LOGGER = Logger.getLogger(ProfesorController.class .getName()); | ||
| + | </ | ||
| + | |||
| + | Veamos ahora el tratamiento individualizado para cada una de ellas: | ||
| + | ==== RuntimeException ==== | ||
| + | Si la excepción es de tipo '' | ||
| + | <code java> | ||
| + | throw ex; | ||
| + | </ | ||
| + | |||
| + | ==== Exception ==== | ||
| + | Si la excepción es de tipo '' | ||
| + | <code java> | ||
| + | throw new RuntimeException(ex); | ||
| + | </ | ||
| + | |||
| + | De esa forma evitamos la obligación de tratar las Checked Exceptions de Java cuando no sabemos que hacer con ella. | ||
| + | |||
| + | ==== javax.validation.ConstraintViolationException ==== | ||
| + | Esta excepción la vamos a reconvertir en una '' | ||
| + | <code java> | ||
| + | throw new BussinessException(cve); | ||
| + | </ | ||
| + | |||
| + | ==== org.hibernate.exception.ConstraintViolationException ==== | ||
| + | Esta excepción la vamos a reconvertir en una '' | ||
| + | <code java> | ||
| + | throw new BussinessException(cve); | ||
| + | </ | ||
| + | |||
| + | ==== BussinessException | ||
| + | Si se produce ésta excepción simplemente la volveremos a relanzar sin modificar nada ya que queremos que se //vea// tal y como es. | ||
| + | <code java> | ||
| + | throw ex; | ||
| + | </ | ||
| + | |||
| + | |||
| + | |||
| + | <note tip> | ||
| + | Al poner todos los '' | ||
| + | </ | ||
| + | |||
| + | ===== Mejoras con BusinessException ===== | ||
| + | En el apartado anterior hemos visto cómo tratamos las siguientes excepciones transformándolas en '' | ||
| + | * <javadoc jee6> | ||
| + | * <javadoc h41> | ||
| + | |||
| + | El significado de la nueva excepción es avisarnos de que los datos de las entidades contienen algún tipo de error y que el usuario debe modificarlo. Por ello la excepción hereda de <javadoc jdk7> | ||
| + | |||
| + | Además de éso, el usar '' | ||
| + | * [[#Unificar el tratamiento de las excepciones]] | ||
| + | * [[# | ||
| + | * [[#Mejorar los mensajes de error]] | ||
| + | |||
| + | ==== Unificar el tratamiento de las excepciones ==== | ||
| + | Desde otras clases de la aplicación , ahora al llamar a los métodos | ||
| + | |||
| + | <note tip> | ||
| + | Otra ventaja de la unificación de excepciones es que si nuestra aplicación dejará de usar Hibernate para volver a JDBC podríamos seguir usando la nueva '' | ||
| + | </ | ||
| + | |||
| + | ==== Simplificar el uso de javax.validation.ConstraintViolationException ==== | ||
| + | El uso de <javadoc jee6> | ||
| + | |||
| + | ¿Qué hace el método <javadoc jee6> | ||
| + | ¿Y <javadoc jee6> | ||
| + | Etc. | ||
| + | Si la mayoría de estos métodos no los vamos a usar , mejor será crear una clase que contenga sólo la información importante ya que ayudará a los programadores a usarla mas rápidamente y sin errores. | ||
| + | |||
| + | Por ello '' | ||
| + | |||
| + | En el siguiente diagrama UML podemos ver las similitudes y diferencias: | ||
| + | |||
| + | <uml> | ||
| + | class BussinessException | ||
| + | BussinessException : Set< | ||
| + | |||
| + | class BussinessMessage | ||
| + | |||
| + | BussinessMessage : getFieldName() | ||
| + | BussinessMessage : getMessage() | ||
| + | |||
| + | BussinessException " | ||
| + | |||
| + | class ConstraintViolationException | ||
| + | ConstraintViolationException : Set< | ||
| + | |||
| + | class ConstraintViolation | ||
| + | |||
| + | ConstraintViolation: | ||
| + | ConstraintViolation: | ||
| + | ConstraintViolation: | ||
| + | ConstraintViolation: | ||
| + | ConstraintViolation: | ||
| + | ConstraintViolation: | ||
| + | ConstraintViolation: | ||
| + | ConstraintViolation: | ||
| + | |||
| + | ConstraintViolationException " | ||
| + | |||
| + | </ | ||
| + | |||
| + | Como podemos ver , aunque hayamos perdido cierta funcionalidad que debe encontrase en <javadoc jee6> | ||
| + | ==== Mejorar los mensajes de error ==== | ||
| + | Al mostrar los mensajes de error que se incluyen en <javadoc jee6> | ||
| + | * El nombre de los campos es el de las propiedades Java y no un nombre amigable para el usuario. | ||
| + | * Los mensajes empiezan en minúscula. | ||
| + | |||
| + | Un ejemplo de mensaje de error es el siguiente: | ||
| + | ape1 - el tamaño tiene que estar entre 3 y 50 | ||
| + | | ||
| + | ¿Qué es " | ||
| + | |||
| + | Así que el mensaje correcto debería ser: | ||
| + | 1º Apellido - El tamaño tiene que estar entre 3 y 50 | ||
| + | |||
| + | <note tip> | ||
| + | Se podría argumentar que esos dos problemas deben ser de la capa de presentación y no de la capa de negocio. Pero ya que <javadoc jee6> | ||
| + | </ | ||
| + | |||
| + | ===== El código de BussinessException ===== | ||
| + | Ya hemos visto las ventajas y el diseño de '' | ||
| + | |||
| + | <code java 1 | BussinessException.java > | ||
| + | public class BussinessException extends Exception { | ||
| + | |||
| + | private Set< | ||
| + | |||
| + | public BussinessException(List< | ||
| + | this.bussinessMessages.addAll(bussinessMessages); | ||
| + | } | ||
| + | |||
| + | public BussinessException(BussinessMessage bussinessMessage) { | ||
| + | this.bussinessMessages.add(bussinessMessage); | ||
| + | } | ||
| + | |||
| + | public BussinessException(Exception ex) { | ||
| + | bussinessMessages.add(new BussinessMessage(null, | ||
| + | } | ||
| + | |||
| + | public BussinessException(javax.validation.ConstraintViolationException cve) { | ||
| + | for (ConstraintViolation constraintViolation : cve.getConstraintViolations()) { | ||
| + | String fieldName; | ||
| + | String message; | ||
| + | |||
| + | fieldName = getCaptions(constraintViolation.getRootBeanClass(), | ||
| + | message = constraintViolation.getMessage(); | ||
| + | |||
| + | bussinessMessages.add(new BussinessMessage(fieldName, | ||
| + | } | ||
| + | } | ||
| + | |||
| + | public BussinessException(org.hibernate.exception.ConstraintViolationException cve) { | ||
| + | bussinessMessages.add(new BussinessMessage(null, | ||
| + | } | ||
| + | |||
| + | public Set< | ||
| + | return bussinessMessages; | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Podemos ver que el código es bastante sencillo. Consta principalmente de varios constructores y el método '' | ||
| + | * Línea 3: Un < | ||
| + | * Líneas 5-7: Constructor al que directamente se le pasa una lista de '' | ||
| + | * Líneas 9-11: Constructor al que directamente se le pasa un único '' | ||
| + | * Líneas 13-15: Constructor al que se le pasa una <javadoc jdk7> | ||
| + | * Líneas 13-15: Constructor al que se le pasa una <javadoc jee6> | ||
| + | * Líneas 29-31: Constructor al que se le pasa una <javadoc h41> | ||
| + | * Línea 33-35: Retorna la lista de todos los '' | ||
| + | |||
| + | El código de '' | ||
| + | <code java 1 | BussinessMessage.java> | ||
| + | public class BussinessMessage implements Comparable< | ||
| + | private final String fieldName; | ||
| + | private final String message; | ||
| + | |||
| + | public BussinessMessage(String fieldName, String message) { | ||
| + | if (message==null) { | ||
| + | throw new IllegalArgumentException(" | ||
| + | } | ||
| + | | ||
| + | if ((fieldName!=null) && (fieldName.trim().equals("" | ||
| + | this.fieldName =null; | ||
| + | } else { | ||
| + | this.fieldName = StringUtils.capitalize(fieldName); | ||
| + | } | ||
| + | this.message = StringUtils.capitalize(message); | ||
| + | } | ||
| + | |||
| + | @Override | ||
| + | public String toString() { | ||
| + | if (fieldName!=null) { | ||
| + | return "'" | ||
| + | } else { | ||
| + | return message; | ||
| + | } | ||
| + | } | ||
| + | | ||
| + | /** | ||
| + | * @return the fieldName | ||
| + | */ | ||
| + | public String getFieldName() { | ||
| + | return fieldName; | ||
| + | } | ||
| + | |||
| + | /** | ||
| + | * @return the message | ||
| + | */ | ||
| + | public String getMessage() { | ||
| + | return message; | ||
| + | } | ||
| + | |||
| + | |||
| + | @Override | ||
| + | public int compareTo(BussinessMessage o) { | ||
| + | if ((getFieldName()==null) && (o.getFieldName()==null)) { | ||
| + | return getMessage().compareTo(o.getMessage()); | ||
| + | } else if ((getFieldName()==null) && (o.getFieldName()!=null)) { | ||
| + | return 1; | ||
| + | } else if ((getFieldName()!=null) && (o.getFieldName()==null)) { | ||
| + | return -1; | ||
| + | } else if ((getFieldName()!=null) && (o.getFieldName()!=null)) { | ||
| + | if (getFieldName().equals(o.getFieldName())) { | ||
| + | return getMessage().compareTo(o.getMessage()); | ||
| + | } else { | ||
| + | return getFieldName().compareTo(o.getFieldName()); | ||
| + | } | ||
| + | } else { | ||
| + | throw new RuntimeException(" | ||
| + | } | ||
| + | } | ||
| + | |||
| + | | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | La clase '' | ||
| + | * Línea 1: Implementar el interfaz < | ||
| + | * Líneas 13 y 15 : Se pone en mayúsculas el primer carácter del campo y el mensaje para que quede mas amigable al usuario. | ||
| + | * Línea de la 43 a la 59: Método < | ||
| + | ===== Nombres de las columnas ===== | ||
| + | La última parte que nos queda por tratar es el tema del nombre de las columnas. En el texto que muestra '' | ||
| + | |||
| + | Una sencilla solución es añadir una nueva anotación a cada propiedad que incluya un nombre amigable de dicha propiedad. La nueva anotación se llamará '' | ||
| + | |||
| + | <code java 1 | Caption.java> | ||
| + | @Target({ElementType.FIELD, | ||
| + | @Retention(RetentionPolicy.RUNTIME) | ||
| + | public @interface Caption { | ||
| + | String value(); | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Ahora nuestras clase de dominio pueden quedar de la siguiente forma: | ||
| + | <code java 1 | Usuario.java> | ||
| + | public class Profesor implements Serializable | ||
| + | | ||
| + | private int id; | ||
| + | @NotBlank | ||
| + | @Caption(" | ||
| + | private String nombre; | ||
| + | | ||
| + | @NotBlank | ||
| + | @Caption(" | ||
| + | private String ape1; | ||
| + | | ||
| + | @Caption(" | ||
| + | private String ape2; | ||
| + | |||
| + | public Profesor(){ | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Vemos en las líneas 5, 9 y 12 como se ha añadido la nueva anotación '' | ||
| + | |||
| + | <note tip> | ||
| + | Vuelvo a insistir que ciertos frameworks de la capa de presentación hacen este mismo trabajo y se puede pensar que dicha anotación no debería estar en la capa de negocio. | ||
| + | </ | ||
| + | |||
| + | Ahora debemos crear el código Java que en función de una clase de dominio y el nombre de la columna nos retorna el // | ||
| + | <code java 1> | ||
| + | private String getCaptions(Class clazz, Path path) { | ||
| + | StringBuilder sb = new StringBuilder(); | ||
| + | if (path != null) { | ||
| + | Class currentClazz = clazz; | ||
| + | for (Path.Node node : path) { | ||
| + | ClassAndCaption clazzAndCaption = getSingleCaption(currentClazz, | ||
| + | if (clazzAndCaption.caption != null) { | ||
| + | if (sb.length() != 0) { | ||
| + | sb.append(" | ||
| + | } | ||
| + | if (node.isInIterable()) { | ||
| + | if (node.getIndex() != null) { | ||
| + | sb.append(node.getIndex()); | ||
| + | sb.append(" | ||
| + | sb.append(clazzAndCaption.caption); | ||
| + | } else if (node.getKey() != null) { | ||
| + | sb.append(clazzAndCaption.caption); | ||
| + | sb.append(" | ||
| + | sb.append(node.getKey()); | ||
| + | } else { | ||
| + | sb.append(clazzAndCaption.caption); | ||
| + | } | ||
| + | } else { | ||
| + | sb.append(clazzAndCaption.caption); | ||
| + | } | ||
| + | } else { | ||
| + | sb.append("" | ||
| + | } | ||
| + | currentClazz = clazzAndCaption.clazz; | ||
| + | } | ||
| + | |||
| + | return sb.toString(); | ||
| + | |||
| + | } else { | ||
| + | return null; | ||
| + | } | ||
| + | |||
| + | } | ||
| + | |||
| + | private ClassAndCaption getSingleCaption(Class clazz, String fieldName) { | ||
| + | ClassAndCaption clazzAndCaptionField; | ||
| + | ClassAndCaption clazzAndCaptionMethod; | ||
| + | |||
| + | if ((fieldName == null) || (fieldName.trim().equals("" | ||
| + | return new ClassAndCaption(clazz, | ||
| + | } | ||
| + | |||
| + | clazzAndCaptionField = getFieldCaption(clazz, | ||
| + | if ((clazzAndCaptionField != null) && (clazzAndCaptionField.caption != null)) { | ||
| + | return clazzAndCaptionField; | ||
| + | } | ||
| + | |||
| + | clazzAndCaptionMethod = getMethodCaption(clazz, | ||
| + | if ((clazzAndCaptionMethod != null) && (clazzAndCaptionMethod.caption != null)) { | ||
| + | return clazzAndCaptionMethod; | ||
| + | } | ||
| + | |||
| + | if (clazzAndCaptionField != null) { | ||
| + | return new ClassAndCaption(clazzAndCaptionField.clazz, | ||
| + | } else if (clazzAndCaptionMethod != null) { | ||
| + | return new ClassAndCaption(clazzAndCaptionMethod.clazz, | ||
| + | } else { | ||
| + | return new ClassAndCaption(clazz, | ||
| + | } | ||
| + | } | ||
| + | |||
| + | private ClassAndCaption getFieldCaption(Class clazz, String fieldName) { | ||
| + | Field field = ReflectionUtils.findField(clazz, | ||
| + | if (field == null) { | ||
| + | return null; | ||
| + | } | ||
| + | |||
| + | Caption caption = field.getAnnotation(Caption.class); | ||
| + | if (caption != null) { | ||
| + | return new ClassAndCaption(field.getType(), | ||
| + | } else { | ||
| + | return new ClassAndCaption(field.getType(), | ||
| + | } | ||
| + | |||
| + | |||
| + | } | ||
| + | |||
| + | private ClassAndCaption getMethodCaption(Class clazz, String methodName) { | ||
| + | String suffixMethodName = StringUtils.capitalize(methodName); | ||
| + | Method method = ReflectionUtils.findMethod(clazz, | ||
| + | if (method == null) { | ||
| + | method = ReflectionUtils.findMethod(clazz, | ||
| + | if (method == null) { | ||
| + | return null; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | Caption caption = method.getAnnotation(Caption.class); | ||
| + | if (caption != null) { | ||
| + | return new ClassAndCaption(method.getReturnType(), | ||
| + | } else { | ||
| + | return new ClassAndCaption(method.getReturnType(), | ||
| + | } | ||
| + | |||
| + | |||
| + | } | ||
| + | |||
| + | private class ClassAndCaption { | ||
| + | |||
| + | Class clazz; | ||
| + | String caption; | ||
| + | |||
| + | public ClassAndCaption(Class clazz, String caption) { | ||
| + | this.clazz = clazz; | ||
| + | this.caption = caption; | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Este código lo deberemos incluir en la clase '' | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | Por último debemos modificar la línea 22 de '' | ||
| + | <code java> | ||
| + | fieldName=constraintViolation.getPropertyPath().toString(); | ||
| + | </ | ||
| + | para que quede de la siguiente forma: | ||
| + | <code java> | ||
| + | fieldName=getCaptions(constraintViolation.getRootBeanClass(), | ||
| + | </ | ||
| + | |||
| + | < | ||
| + | Para la realización de éste código se han usado las clases <javadoc s31> | ||
| + | </ | ||