Aunque tenemos filtros personalizados, en este articulo, vamos a aplicar los filtros que nos ofrece Jakarta Bean Validation 3.0(JSR380). Recordad que estamos trabajando con Java 17 y Spring Boot 3, ya que es la limitación que tenemos.
Las dependencias
Lo primero que necesitamos incluir en nuestro pom.xml es la dependencia a la libreria de jakarta.validation, y eso lo podemos hacer añadiendo
<!-- *********************Validaciones ***************** -->
<!--
https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Las anotaciones
Basado en https://jakarta.ee/specifications/bean-validation/3.0/jakarta-bean-validation-spec-3.0.html
Con esta librería disponemos de
- @NotNull – Comprueba que la propiedad no sea nula
- @AssertTrue – Comprueba que la propiedad sea True
- @AssertFalse – Comprueba que la propiedad sea False
- @Size – Comprueba que esta propiedad (String, Collection, Map, Array) cumpla con los valores min y max indicados
- @Min(value=..)– Comprueba que la propiedad no es menor que el valor indicado
- @Max(value=..) – Comprueba que la propiedad no sea mayor que el valor indicado
- @Email – Valida que el formato de la propiedad sea de una direccion Email
- @NotEmpty – Comprueba que la propiedad (String, Collection, Map o Array) no sea nulo o vacío
- @NotBlank – Para campos de texto, comprueba que no sean nulos o blancos
- @Positive o @PositiveOrZero comprueba que el valor es positivo o, positivo o cero
- @Negative o @NegativeOrZero comprueba que el valor es negativo o, negativo o cero
- @Past y @PastOrPresent comprueba que la fecha es anterior o, anterior o actual
- @Future y @FutureOrPresent comprueba que la fecha es posterior o, posterior o actual
- @Pattern(regexp=»regexp», flag=) o @Patterns( {@Pattern(…)} )– Comprueba que la propiedad coincida con el pattern indicado
- @DecimalMin y @DecimalMax: indican el valor mínimo y/o máximo permitido para un atributo de tipo BigDecimal.
- @Digits(integerDigits=1) – Número o string, comprueba que sea numérico con un máximo de enteros indicado en integerDigits y decimales en fractionalDigits
- @Valid – Realiza las validaciones del objeto indicado, recursivamente
Además, podemos utilizar estas validaciones dentro de colecciones o de campos Optional
List<@NotBlank String> preferences;
public Optional<@Past LocalDate> getDateOfBirth()
...
Tambien tenemos que considerar que todas aquellas validaciones que podemos escribir nosotros, y que comentamos en :
- Creacion de anotaciones para filtros
- Añadiendo un filtro de comprobación de existencia en tabla por anotaciones, en Spring
- Creando un filtro personalizado de fecha con mensaje segun error
se pueden utilizar en las mismas condiciones
Utilizando los filtros en un Entity
Quizás, el añadir filtros a un Entity puede que sea la primera idea que tengamos, pero empecemos a trabajar correctamente, por ejemplo la Entity de Cine solo deberíamos utilizarla para relacionarnos con la BBDD, para todo lo demás, debemos utilizar un DTO, y será en CineDTO que podríamos filtrar ci_capacidad para que siempre fuera positivo y menos de 1000; solo deberíamos añadir
@Column(nullable = false)
@Positive
@DecimalMax(value = "1000")
private int ci_capacidad;
Para que ese filtro se tuviera en cuenta , podríamos añadir en el controller la anotación @Valid, y proporcionar un Mapped para convertir CineDTO en Cine
@PostMapping
public ResponseEntity<Map<String, Object>> alta(@Valid @RequestBody CineDTO c)
throws DomainException, ControllerException, DAOException { // ID,NOMBRE,DESCRIPCION
Map<String, Object> map = new LinkedHashMap<String, Object>();
c.setId_cine(0);
try {
Cine cine = cDao.insert(convertToEntity(c));
map.put("status", 1);
map.put("message", "Registro salvado");
return new ResponseEntity<>(map, HttpStatus.OK);
} catch (Exception ex) {
throw new ControllerException("Error al hacer la insercion " + ex.getMessage());
}
}
y si queremos que esa comprobacion se repita al actualizar, lo volveremos a indicar en el controller
@PutMapping
public ResponseEntity<Map<String, Object>> modificacion(@Valid @RequestBody CineDTO c)
throws ControllerException, DomainException, DAOException {
Map<String, Object> map = new LinkedHashMap<String, Object>();
if (cDao.update(convertToEntity(c))==true) {
map.put("status", 1);
map.put("message", "Actualizacion correcta");
return new ResponseEntity<>(map, HttpStatus.OK);
} else {
throw new ControllerException("Error al hacer la modificacion " + c.toString() );
}
}
Con eso se activaría la revisión de las anotaciones indicadas en CineDTO.
Debemos tener en cuenta, que son validaciones que se realizan al construir el objeto, no al construir la tabla y lo señalo para diferenciarlas de, por ejemplo
@Column(nullable = true, length = 100)
Que es una anotación de base de datos. Esto significa, que si deseamos que ci_calle no se pueda dejar en blanco, independiente de lo que diga la base de datos, podemos utilizar @NotEmpty
@Column(nullable = true, length = 100)
@NotEmpty
private String ci_calle;
Esa diferenciación, hace, precisamente la conveniencia de partir los filtros en dos objetos, dejando en el Entity solo los filtros de JPA para creación de BBDD (Si es que acaso vais a crear vosotros la BBDD, cosa que en producción no es fácil que pase) por lo que yo os invitaría a dejar el Entity sin ninguna anotación de constructor y centrar en el DTO los filtros
Por último, recordemos de crear los métodos para las dos conversiones
private CineDTO convertToDto(Cine cine) {
CineDTO cineDTO = mapper.map(cine, CineDTO.class);
return cineDTO;
}
private Cine convertToEntity(CineDTO cineDTO) {
Cine cine = mapper.map(cineDTO, Cine.class);
return cine;
}
Ahora, si intentamos crear un cine con una capacidad mayor que 1000, o sin dirección, recibiremos un error automáticamente
Tened en cuenta que esta forma de presentar los errores, se consigue con los @ControllerAdvice que comentamos en Añadiendo un @ControllerAdvice. Aclarando mensajes de error
Si no trabajas con API
Para el caso que no trabajes con una API, o, si prefieres controlar las respuestas de otra forma; puedes evitar el error «MethodArgumentNotValidException» que te genera cuando no cumples con el formato.
Para ello, solo has de incluir en la recepción un BindingResult, y luego puedes obtener los errores explotando el campo que te entrega.
En el siguiente ejemplo, cuando hay un error en la entrada, imprimo en consola el mensaje de error, el valor del campo, y, a continuación, lanzo una excepcion
@PostMapping
public ResponseEntity<Map<String, Object>> alta(@Valid @RequestBody CineDTO c, BindingResult result) throws DomainException, ControllerException, DAOException { //ID,NOMBRE,DESCRIPCION
Map<String, Object> map = new LinkedHashMap<String, Object>();
if (result.hasErrors()) {
System.out.println("***************************************************");
System.out.println(result.getFieldError("ci_capacidad"));
System.out.println(result.getFieldValue("ci_capacidad"));
System.out.println("***************************************************");
throw new ControllerException("Error al hacer la insercion");
}
c.setId_cine(0);
c=cDao.insert(convertToEntity(c));
Para un funcionamiento eficiente, deberia añadir una pregunta para comprobar el campo erróneo, pero como trabajo con API, solo lo he escrito para presentarlo, y, mantengo la version original, sin el BindingResult
Conclusión
Todo este desarrollo lo teneis explicado con mas detalle en youTube, y, aunque es conveniente que intentéis escribirlo TODO vosotros, si queréis renunciar a ello, lo teneis tambien en GitHub
Este desarrollo esta hecho para disponer de un fuente para explicar otros temas, tal y como se indica en Visión de conjunto con Spring
Relacionado
Descubre más desde Recursos para formacion
Suscríbete y recibe las últimas entradas en tu correo electrónico.