unidades:07_arquitectura:02_excepciones
no way to compare when less than two revisions
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> | ||
+ | </ |
unidades/07_arquitectura/02_excepciones.txt · Última modificación: 2023/04/07 21:26 por 127.0.0.1