Herramientas de usuario

Herramientas del sitio


unidades:06_objetos_validaciones:02_validaciones

Validaciones

En este tema vamos a ver cómo Hibernate puede validar los datos de nuestros objetos Java antes de que se persistan en la base de datos.

Hace unos años se creó una especificación estándar para validar los valores de los objetos Java mediante anotaciones. Esta especificación se define en el JSR 303: Bean Validation.

Instalación

Hibernate soporta estas anotaciones aunque el desarrollo del código que las soporta ha sido desarrollado como un subproyecto llamado Hibernate Validator. Ésto hace que sea necesario bajarse los JARs de Hibernate Validator e incluirlos en nuestro proyecto.

Desde la URL http://sourceforge.net/projects/hibernate/files/hibernate-validator/4.3.0.Final/ nos podemos bajar el fichero hibernate-validator-4.3.0.Final-dist.zip con la versión 4.3.0 de Hibernate Validator.

Deberemos descomprimir el fichero hibernate-validator-4.3.0.Final-dist.zip y añadir los JAR que se encuentran en la carpeta dist a nuestro proyecto tal y como hemos hecho con los JAR de Hibernate.

  • \dist\hibernate-validator-4.3.0.Final.jar
  • \dist\hibernate-validator-annotation-processor-4.3.0.Final.jar
  • \dist\lib\optional\joda-time-1.6.jar
  • \dist\lib\optional\jsoup-1.6.1.jar
  • \dist\lib\required\validation-api-1.0.0.GA.jar
Se deben copiar todos los ficheros excepto los siguientes:
  • \dist\lib\optional\hibernate-jpa-2.0-api-1.0.1.Final.jar
  • \dist\lib\required\jboss-logging-3.1.0.CR2.jar
  • \dist\lib\optional\log4j-1.2.16.jar
No bajaros la versión Hibernate Validator 5.0.0.Final. Esta se hizo estable solo 2 semanas antes de empezar el curso.Así que no la usaremos.

Introducción

Empezar a usar las validaciones mediate anotaciones es tan sencillo como añadir dichas anotaciones a las propiedades de las clases Java a validar.

Veamos un sencillo ejemplo con la clase Profesor.

1
import java.io.Serializable;
import java.util.Set;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
 
 
public class Profesor implements Serializable  {
 
    private int id;
    @NotNull
    @Size(min = 3, max = 50)    
    private String nombre;
    @NotNull
    @Size(min = 3, max = 50)
    private String ape1;
    @Size(min = 3, max = 50)    
    private String ape2;
 
 
    public Profesor(){ 
 
    }
 
    public Profesor(String nombre, String ape1, String ape2) {
        this.nombre = nombre;
        this.ape1 = ape1;
        this.ape2 = ape2;
    }
}

Al código fuente de la clase profesor se han añadido las siguientes nuevas anotaciones @NotNull y @Size.

  • @NotNull: Esta anotación indicará que el valor de la propiedad a la que se le aplica no puede ser null.
  • @Size(min=n,max=m): Esta anotación indicará ,para una propiedad de tipo String que no sea null, que el tamaño mínimo deberá ser n caracteres y el máximo deberá ser de m caracteres.

Expliquemos ahora cada una de las validaciones de la clase Profesor.

  • La propiedad nombre no puede ser null (línea 10).
  • La propiedad nombre debe tener una longitud mínima de 3 caracteres y máximo de 50 (línea 11).
  • La propiedad ape1 no puede ser null (línea 13).
  • La propiedad ape1 debe tener una longitud mínima de 3 caracteres y máximo de 50 (línea 14).
  • La propiedad ape2 debe tener una longitud mínima de 3 caracteres y máximo de 50 (línea 16).
No se ha validado que ape2 no pueda ser null ya que en otros países no existe el 2º apellido.

Ahora cuando vayamos a persistir la clase Profesor se lanzará la excepción javax.validation.ConstraintViolationException en caso de que alguna de las validaciones no se cumpla.

Veamos cómo queda el código Java al persistir a Profesor.

1
Profesor profesor = new Profesor("ca", null, "Gomez");
 
try {
    session.beginTransaction();
 
    session.save(profesor);
 
    session.getTransaction().commit();
} catch (ConstraintViolationException cve) {
    session.getTransaction().rollback();
    System.out.println("No se ha podido insertar el profesor");
}

Al ejecutar el código se mostrará al usuario el mensaje No se ha podido insertar el profesor ya que la propiedad ape1 tiene el valor null.

Destacar que prácticamente no se ha modificado el código Java al persistir la clase Profesor. Ahora simplemente tenemos que gestionar la excepción ConstraintViolationException pero no tenemos que llamar directamente a ningún código para que realice las validaciones. Por suerte Hibernate lo realiza automáticamente. Ësto último es de gran importancia ya que si tuviéramos que llamar directamente a un hipotético método validate() sería propenso a que lo olvidáramos en algún momento y podríamos acabar con datos erróneos en la base de datos.

Mensajes

En el ejemplo anterior se muestra un simple mensaje al usuario indicando que no ha podido insertarse el profesor pero sin indicar el motivo de ello. Como todos sabemos, se debe mostrar al usuario los motivos por lo que ha fallado dicha inserción 1).

La excepción ConstraintViolationException contiene la información con todas las validaciones que se han infringido, incluyendo el mensaje de error y la propiedad Java sobre la que se ha producido. Para ello llamaremos al método getConstraintViolations() que nos retorna un java.util.Set de objetos javax.validation.ConstraintViolation.

La clase ConstraintViolation contiene la información de una validación que se ha infringido. Los métodos más importantes que debemos usar son los siguientes:

Ahora el código Java debe quedar de la siguiente forma:

1
Profesor profesor = new Profesor("ca", null, "Gomez");
 
try {
    session.beginTransaction();
 
    session.save(profesor);
 
    session.getTransaction().commit();
} catch (ConstraintViolationException cve) {
    session.getTransaction().rollback();
    System.out.println("No se ha podido insertar el profesor debido a los siguientes errores:");
    for (ConstraintViolation constraintViolation : cve.getConstraintViolations()) {
        System.out.println("En el campo '" + constraintViolation.getPropertyPath() + "':" + constraintViolation.getMessage());
    }                
}

Como podemos ver, se han añadido las líneas 12, 13, y 14. Estas líneas contienen un bucle para recorrer cada una de las validaciones infringidas y mostrar el mensaje al usuario.

La salida por la consola en este caso será la siguiente:

No se ha podido insertar el profesor debido a los siguientes errores.
En el campo 'ape1':no puede ser null
En el campo 'nombre':el tamaño tiene que estar entre 3 y 50

Mensajes personalizados

En el ejemplo anterior podemos ver cómo el mensaje de la anotación @NotNull es “no puede ser null”. Personalmente considero que a un usuario nunca habría que nombrarle la palabra null ya que sólo tiene sentido para los informáticos. Por suerte podemos cambiar los mensajes que se muestran al usuario.

Existen dos formas de cambiar los mensajes al usuario:

Reemplazo local

Para cada anotación de validación es posible usar el atributo message para personalizar el mensaje que usa esa anotación concreta en ese atributo concreto.

Veamos cómo queda la clase Profesor:

1
import java.io.Serializable;
import java.util.Set;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
 
 
public class Profesor implements Serializable  {
 
    private int id;
    @NotNull
    @Size(min = 3, max = 50)    
    private String nombre;
    @NotNull(message="No puede estar vacío")
    @Size(min = 3, max = 50)
    private String ape1;
    @Size(min = 3, max = 50)    
    private String ape2;
 
 
    public Profesor(){ 
 
    }
 
    public Profesor(String nombre, String ape1, String ape2) {
        this.nombre = nombre;
        this.ape1 = ape1;
        this.ape2 = ape2;
    }
}

Vemos cómo en la línea 13 se ha modificado la anotación @NotNull para incluir el mensaje a mostrar al usuario.

Si volvemos a ejecutar el código la salida por consola será ahora:

No se ha podido insertar el profesor debido a los siguientes errores:
En el campo 'nombre':el tamaño tiene que estar entre 3 y 50
En el campo 'ape1':No puede estar vacío

El problema de esta técnica es el nuevo mensaje.Sólo se aplica a ape1 con la validación @NotNull, pero si el campo nombre estuviera a null seguiría saliendo el mensaje original.

Reemplazo global

Por suerte hay una forma de cambiar los mensajes para todas las veces que aparece la validación @NotNull o cualquier otra anotación de validación.

Debemos crear el fichero ValidationMessages.properties en el paquete raiz, insertar una línea con la FQCN de la anotación de la que queremos cambiar el mensaje , prefijar el texto “.messaje=” y añadir a renglón seguido el muevo mensaje.

Veamos un ejemplo que quedará mucho más claro:

| Fichero ValidationMessages.properties
javax.validation.constraints.NotNull.message=No puede estar vacio

Ahora con el siguiente código

1
Profesor profesor = new Profesor(null, null, "Gomez");
 
try {
    session.beginTransaction();
 
    session.save(profesor);
 
    session.getTransaction().commit();
} catch (ConstraintViolationException cve) {
    session.getTransaction().rollback();
    System.out.println("No se ha podido insertar el profesor debido a los siguientes errores:");
    for (ConstraintViolation constraintViolation : cve.getConstraintViolations()) {
        System.out.println("En el campo '" + constraintViolation.getPropertyPath() + "':" + constraintViolation.getMessage());
    }                
}

Como el objeto profesor tiene los 2 campos nombre y ape1 a null (línea 1), al ejecutarse el código la salida por consola es:

No se ha podido insertar el profesor debido a los siguientes errores:
En el campo 'nombre':No puede estar vacio
En el campo 'ape1':No puede estar vacío

Es decir que a partir de ahora cualquier uso que hagamos de la anotación @NotNull hará uso del nuevo mensaje.

Y ahora ya podemos quitar el mensaje personalizado de la línea 13 de la clase Profesor ya que no es necesario.

Validaciones simples

Hasta ahora sólo hemos usado las anotaciones @NotNull y @Size. Veamos una lista con más anotaciones para validar:

Anotación Tipos 2) Explicación
@AssertFalse Boolean, boolean El booleano deberá ser false
@AssertTrue Boolean, boolean El booleano deberá ser true
@Digits(integer=n, fraction=m) Cualquier tipo numérico Especifica el nº máximo de cifras enteras y decimales
@Future java.util.Date y java.util.Calendar La fecha debe ser mayor que ahora
@Past java.util.Date y java.util.Calendar La fecha debe ser menor que ahora
@Max(n) Cualquier tipo numérico El valor deberá ser menor o igual a n
@Min(n) Cualquier tipo numérico El valor deberá ser mayor o igual a n
@NotNull Object El objeto no puede ser null
@Null Object El objeto debe ser null
@Pattern(regexp=“r”) String Comprueba que el valor se ajusta a la expresión regular r
@Size(min=n, max=m) String o colecciones El tamaño del String o la colección debe estar entre n y m.
@Email String El valor tiene el formato de una dirección de correo electrónico
@NotBlank String Comprueba que el String no sea null y que al hacer un trim() aún haya algún caracter
@Valid Object Si el campo es otro objeto que tiene sus propias validaciones, con esta anotación indicaremos que también debe validarse ese otro objeto

Como podemos ver, las anotaciones son bastante sencillas de usar.

Debemos destacar el uso de @NotNull y @NotBlank. En caso de que queramos que un String no esté vació lo normal es que queramos usar @NotBlank ya que esta anotación nos garantiza que no hayan puesto únicamente espacios además de no ser null.
Recuerda que la anotación @Valid es obligatoria ponerla si queremos que se validen las anotaciones que hemos puesto en el otro objeto que está como propiedad del objeto que se está validando

Validaciones complejas

Hasta ahora hemos visto simples validaciones de campos pero , ¿qué ocurre cuando queremos validar más de un campo a la vez?. Hibernate Validator soporta 2 formas de hacerlo.

La anotación @ScriptAssert

La anotación @ScriptAssert nos permitirá ejecutar una expresión en el lenguaje JavaScript 3) usando dentro del código cualquier propiedad de la clase.

Explico la anotación @ScriptAssert porque existe en el estándar pero recomiendo en vez esta anotación , que se use el truco que explico a continuación de esta anotación.

@ScriptAssert debe colocarse a nivel de clase ya que no se refiere a ninguna propiedad concreta .

Un ejemplo es el siguiente:

@ScriptAssert(lang="javascript",script="(_this.password!=null)?  _this.password.equals(_this.confirmPassword) : false",message="Los 2 passwords deben ser iguales")
public class Usuario implements Serializable  {
 
    private int idUsuario;
 
    @NotNull
    @Size(min = 3, max = 50)
 
    @Column(name="login",unique=true)
    private String login; 
 
    @NotNull
    @Size(min = 3, max = 50)    
    private String nombre;
 
    @NotNull
    @Size(min = 3, max = 50)
    private String ape1;
 
    @Size(min = 3, max = 50)  
    private String ape2;
 
    @NotNull
    @Size(min = 7, max = 50)  
    private String password;
 
    @NotNull
    @Size(min = 7, max = 50)  
    private String confirmPassword;    
}

Vemos en la línea 1 cómo se ha añadido la anotación @ScriptAssert con el código JavaScript:

(_this.password!=null)?  _this.password.equals(_this.confirmPassword) : false

Se ha utilizado el operador ternario de JavaScript porque sólo es posible escribir una expresión con lo que no podemos usar un if ni similares, lo que limita un poco el tipo de validaciones que podemos hacer.

Para acceder a los valores nótese que se usa “_this” con un subrayado delante de la palabra this.

Si intentamos añadir más validaciones con @ScriptAssert se producirá un error al compilar indicando que ya existe la anotación @ScriptAssert. Para solucionarlo deberemos poner las anotaciones @ScriptAssert dentro de una anotación @ScriptAssert.List tal y como se ve en el siguiente ejemplo:

@ScriptAssert.List({
  @ScriptAssert(lang="javascript",script="(_this.password!=null)?  _this.password.equals(_this.confirmPassword) : false",message="Los 2 passwords deben ser iguales"),
  @ScriptAssert(lang="javascript",script="(_this.password=='123456')",message="La contraseña no puede ser tan tonta")
})

Métodos Java de Validación

Podemos añadir también métodos Java que realicen las validaciones, usando un pequeño truco.

Este truco que vamos a explicar ahora mismo , es de una sencillez pasmosa pero muy util para colocar las validaciones que debamos hacer en código Java

Veamos ahora un ejemplo de código:

@AssertTrue(message="El login y el password no pueden coincidir")
private boolean isPasswordValido() {
    if ((login!=null) && (login.equalsIgnoreCase(password))) {
        return false;
    } else {
        return true;
    }
}

Deberemos hacer un método Java que realice la validación que deseemos y que retorne true si la validación ha tenido éxito y false si ha fallado. Entonces el truco consiste en añadir la anotación @AssertTrue al método de forma que si éste retorna false no se cumpla la validación de @AssertTrue y nos avise de ello. Por último deberemos añadir un mensaje personalizado a la anotación @AssertTrue.

El nombre del método deberá empezar por is o get ya que en caso contrario no se llamará a dicho método en la validación
El nombre del método no debe llamarse como el de una propiedad. Es decir si está la propiedad password , no se debería llamar isPassword o getPassword pq sino Hibernate se confundirá con los set/get de la propiedad password. Por ello le añadimos el texto “Valido” o lo que queramos.

Esta forma de validar tiene varias ventajas respecto a @ScriptAssert:

  • El código fuente de la validación se comprueba en tiempo de compilación en vez de en tiempo de ejecución.
  • Es posible usar toda la potencia de Java en vez de sólo expresiones en JavaScript.
  • En caso de tener muchas validaciones queda todo mucho más ordenado mediante métodos que con multitud de anotaciones @AssertTrue al principio de la clase.
  • Al usar la anotación @ScriptAssert no se obtiene nada mediante el método ConstraintViolation.getPropertyPath() que obtiene el nombre de la propiedad que ha infringido la validación. Sin embargo, validando con métodos al usar ConstraintViolation.getPropertyPath() sí que se retorna el nombre del método sin la palabra is, con lo que se ayuda al usuario a encontrar la causa del error.
El único problema de esta técnica de validación es que al hacer el método privado y que aparentemente nadie lo llame , si usamos herramientas de chequeo de código como Sonar, ésta nos indicará que tenemos un método privado que no usamos.

Pero si lo hacemos público cualquiera podría ver las métodos y hacer uso de ellos cuando realmente no queremos que los llamen directamente.

1)
o actualización , borrado, etc
2)
Tipo de la propiedad a la que se puede aplicar
3)
O cualquier otro lenguaje de Script como Googvy siempre y cuando se instale
unidades/06_objetos_validaciones/02_validaciones.txt · Última modificación: 2023/04/07 21:26 por 127.0.0.1