En este post te mostraré a validar un formulario en Spring Boot y a mostrar los mensajes de error (para indicar en dónde se equivocó al llenarse) con Thymeleaf.
La validación es muy fácil, pues solo se utilizan anotaciones en la entidad.
Verás que es muy sencillo gracias a las anotaciones como Min
, NotNull
y Size
, las cuales se encuentran en:
javax.validation.constraints.*
Dentro de nuestra clase debemos realizar las anotaciones para validar formularios en Spring Boot. Por cada propiedad que haya, hacemos una anotación.
Por ejemplo, veamos esta validación para que la cadena no sea nula:
package hello;
import javax.validation.constraints.NotNull;
public class Producto {
@NotNull
private String nombre;
}
En este caso utilizamos la anotación de @NotNull
justo arriba de la propiedad llamada nombre
.
Las anotaciones también pueden recibir “argumentos”, entre ellos puede ir el mensaje que se muestra en caso de que el dato no sea válido. Por ejemplo:
package hello;
import javax.validation.constraints.NotNull;
public class Producto {
@NotNull(message = "Debes especificar el nombre")
private String nombre;
}
A través de message
indicamos el mensaje que se muestra en caso de que el dato no sea válido al validar formularios en Spring Boot.
Al final del post dejaré un ejemplo completo de otras validaciones para cada propiedad de la clase.
Una vez que hemos definido las validaciones de la clase, es hora de validar en el controlador los datos enviados por formulario.
Para que la validación se realice, debemos indicar la anotación @Valid
al inyectar nuestra entidad o clase, y también debemos inyectar BindingResult.
@PostMapping(value = "/agregar")
public String guardarProducto(@ModelAttribute @Valid Producto producto, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// Aquí se puede hacer cualquier cosa, yo hago una redirección para mostrar los errores en el form
return "productos/agregar_producto";
}
// Si llegamos hasta aquí es que no hubo errores
return "redirect:/productos/mostrar";
}
La verdadera comprobación de que los datos son válidos es al llamar a hasErrors()
, en caso de que los tenga, se recomienda redirigir al usuario al formulario que llenó de manera equivocada.
Si hasErrors()
devuelve false
entonces podemos asumir que los datos del formulario son válidos, o mejor dicho, que cumplieron las validaciones indicadas en la entidad.
Suponiendo que los datos que el usuario introdujo no son válidos, lo vamos a redireccionar al formulario renderizado con Thymeleaf.
Ahora tenemos disponible a #fields
dentro de la vista. En la misma podemos usar th:if (esto es opcional, pues si usamos th:errors
no hace falta el if
)con:
#fields.hasErrors("campo")
Para mostrar el mensaje de error usamos:
th:errors="*{campo}"
Los errores de formulario se pueden mostrar en cualquier lugar apropiado. Veamos un ejemplo:
<form method="post">
<label for="nombre">Nombre del producto</label>
<input th:field="*{nombre}" id="nombre" placeholder="Escribe el nombre del producto" type="text">
<div th:errors="*{nombre}"></div>
</form>
En la línea 4 tenemos un div
que se mostrará si hay errores en el campo nombre
.
Ahora que ya he explicado lo básico, voy a mostrar un ejemplo completo de un sistema que ando haciendo, recuerda que es un ejemplo y que debes entender el código antes de copiarlo y pegarlo.
Comenzando por la clase, que a su vez es una entidad. Se pueden observar todas las validaciones que se hacen a las propiedades, por ejemplo, en el precio o en la existencia.
package hello;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Entity
public class Producto {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@NotNull(message = "Debes especificar el nombre")
@Size(min = 1, max = 50, message = "El nombre debe medir entre 1 y 50")
private String nombre;
@NotNull(message = "Debes especificar el código")
@Size(min = 1, max = 50, message = "El código debe medir entre 1 y 50")
private String codigo;
@NotNull(message = "Debes especificar el precio")
@Min(value = 0, message = "El precio mínimo es 0")
private Float precio;
@NotNull(message = "Debes especificar la existencia")
@Min(value = 0, message = "La existencia mínima es 0")
private Float existencia;
public String getCodigo() {
return codigo;
}
public void setCodigo(String codigo) {
this.codigo = codigo;
}
public Float getPrecio() {
return precio;
}
public void setPrecio(Float precio) {
this.precio = precio;
}
public Float getExistencia() {
return existencia;
}
public void setExistencia(Float existencia) {
this.existencia = existencia;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
Después tenemos el controlador.
Por el momento solo estoy haciendo la validación del formulario en el método guardarProducto
(línea 55) pero como las anotaciones ya están en la clase solo hay que llamara bindingResult.hasErrors
en los otros métodos.
package hello;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.validation.Valid;
@Controller
@RequestMapping(path = "/productos")
public class ProductosController {
@Autowired
private ProductosRepository productosRepository;
@GetMapping(value = "/agregar")
public String agregarProducto(Model model) {
model.addAttribute("producto", new Producto());
return "productos/agregar_producto";
}
@GetMapping(value = "/mostrar")
public String mostrarProductos(Model model) {
model.addAttribute("productos", productosRepository.findAll());
return "productos/ver_productos";
}
@PostMapping(value = "/eliminar")
public String eliminarProducto(@ModelAttribute Producto producto, RedirectAttributes redirectAttrs) {
redirectAttrs
.addFlashAttribute("mensaje", "Eliminado correctamente")
.addFlashAttribute("clase", "warning");
productosRepository.deleteById(producto.getId());
return "redirect:/productos/mostrar";
}
@PostMapping(value = "/editar")
public String actualizarProducto(@ModelAttribute Producto producto, RedirectAttributes redirectAttrs) {
productosRepository.save(producto);
redirectAttrs
.addFlashAttribute("mensaje", "Editado correctamente")
.addFlashAttribute("clase", "success");
return "redirect:/productos/mostrar";
}
@GetMapping(value = "/editar/{id}")
public String mostrarFormularioEditar(@PathVariable int id, Model model) {
model.addAttribute("producto", productosRepository.findById(id));
return "productos/editar_producto";
}
@PostMapping(value = "/agregar")
public String guardarProducto(@ModelAttribute @Valid Producto producto, BindingResult bindingResult, RedirectAttributes redirectAttrs) {
if (bindingResult.hasErrors()) {
return "productos/agregar_producto";
}
productosRepository.save(producto);
redirectAttrs
.addFlashAttribute("mensaje", "Agregado correctamente")
.addFlashAttribute("clase", "success");
return "redirect:/productos/mostrar";
}
}
Finalmente en la vista tenemos el siguiente formulario. Se agrega la clase dinámica is-invalid
en caso de que haya errores, y también se muestra un div
con ese mensaje usando th:errors
.
<!DOCTYPE html>
<html lang="es" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="master">
<body>
<main layout:fragment="contenido">
<div class="col-12">
<h2>Agregar producto</h2>
<form th:object="${producto}" th:action="@{/productos/agregar}" method="post">
<div class="form-group">
<label for="nombre">Nombre del producto</label>
<input th:field="*{nombre}" id="nombre" placeholder="Escribe el nombre del producto" type="text"
class="form-control" th:classappend="${#fields.hasErrors('nombre')} ? 'is-invalid' : ''">
<div class="invalid-feedback" th:errors="*{nombre}"></div>
</div>
<div class="form-group">
<label for="codigo">Código de barras</label>
<input th:field="*{codigo}" id="codigo" placeholder="Escribe el código del producto" type="text"
class="form-control" th:classappend="${#fields.hasErrors('codigo')} ? 'is-invalid' : ''">
<div class="invalid-feedback" th:errors="*{codigo}"></div>
</div>
<div class="form-group">
<label for="existencia">Existencia actual</label>
<input th:field="*{existencia}" id="existencia" placeholder="Cantidad actual del producto" type="number"
class="form-control" th:classappend="${#fields.hasErrors('existencia')} ? 'is-invalid' : ''">
<div class="invalid-feedback" th:errors="*{existencia}"></div>
</div>
<div class="form-group">
<label for="existencia">Precio</label>
<input th:field="*{precio}" id="precio" placeholder="Precio del producto" type="number"
class="form-control" th:classappend="${#fields.hasErrors('precio')} ? 'is-invalid' : ''">
<div class="invalid-feedback" th:errors="*{precio}"></div>
</div>
<button class="btn btn-success" type="submit">Guardar</button>
<a class="btn btn-warning" th:href="@{/productos/mostrar}">Ver todos</a>
</form>
</div>
</main>
</body>
</html>
Estoy usando th:classappend para agregar la clase de error al input de Bootstrap. También estoy usando el div
con la clase invalid-feedback para que la misma se muestre bajo el input
.
Obviamente no necesitas estilos para validar formularios en Spring Boot, pero no pasa nada si los agregas.
Así es como terminamos por hoy. Puedes leer más sobre Spring Boot en mi blog.
Hoy te voy a presentar un creador de credenciales que acabo de programar y que…
Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…
En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…
En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…
Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…
En este artículo te voy a enseñar cómo usar un "top level await" esperando a…
Esta web usa cookies.