Validando dados do formulário

Validar os dados de entrada é uma tarefa muito importante para qualquer aplicação. É uma das principais formas de garantir a integridade dos dados e a segurança da aplicação.

A validação consiste na verificação das informações inseridas pelo usuário, para garantir que elas estão de acordo com o esperado. Por exemplo, se o usuário está inserindo um e-mail, é necessário verificar se o texto inserido possui o formato de um e-mail válido. Outro exemplo, é a validação de campo não vazio, ou seja, obrigar o usuário a inserir alguma informação naquele campo.

Faça um teste e acesse o formulário de adicionar um novo produto, deixe todos os campos vazios e clique em salvar. Perceba que a aplicação não faz nenhuma validação e salva o produto com todos os campos vazios. Isso não é uma abordagem correta, pois pode gerar diversos tipos de problemas na aplicação. Por isso a validação é uma etapa fundamental de qualquer sistema.

Validando o formulário

O Spring Boot possui uma biblioteca chamada spring-boot-starter-validation que facilita bastante o trabalho de validação. Para utilizá-la, basta adicioná-la como dependência no arquivo pom.xml:

1
<dependency>
2
<groupId>org.springframework.boot</groupId>
3
<artifactId>spring-boot-starter-validation</artifactId>
4
</dependency>

Agora, vamos adicionar as anotações de validação nos campos do formulário. Para isso, vamos utilizar a anotação @NotBlank que valida se o campo não está vazio. Essa anotação é do pacote jakarta.validation.constraints, por isso é necessário importá-lo. A anotação possui uma mensagem que será exibida caso o campo esteja vazio definida através do atributo message da anotação.

1
import jakarta.validation.constraints.NotBlank;
2
3
public class Product {
4
private String id;
5
@NotBlank(message = "Nome é obrigatório")
6
private String name;
7
@NotBlank(message = "Descrição é obrigatória")
8
private String description;
9
private double price;
10
11
...
12
}

Modificando a vinculação entre formulário e controlador

Até então estávamos utilizando o @RequestParam para fazer a vinculaçõa entre o formulário e o controlador e não tem nenhum problema nisso, porém existe uma maneira mais fácil para vincular o HTML do formulário com o Controlador e essa forma é utilizada pelo validador do spring.

Em vez de utilizar o @RequestParam e pegar individualmente cada campo do formulário podemos vincular esass duas pontas através da anotação @ModelAttribute no controlador e a utilização da diretriz th:object e th:field no formulário.

Primeiramente vamos alterar o controlador:

1
@PostMapping("/")
2
public String store(@ModelAttribute Product product) {
3
...
4
}

Dessa maneira o Spring vai tentar transformar diretamente os dados do formulário em um objeto do tipo Product. A segunda modificação é no formulário:

1
<form action="/products/" method="post" th:object="${product}">
2
3
<label for="title">Título</label>
4
<input type="text" th:field="${product.title}" placeholder="title" id="title">
5
6
7
<label for="price">Preço</label>
8
<input type="number" th:field="${product.price}" placeholder="price" id="price" step="any">
9
10
...
11
12
</form>

Veja que adicionamos o th:object diretamente na tag form, enquanto que a o th:field foi substituido pelo atributo name do form. Desta maneira fizemos a vinculação (binding) entre o formulário HTML e o controlador Java.

Validando o objeto recebido no controlador

Apenas a adição das anotações no model não é suficiente para que a validação funcione. É necessário alterar o método store do controller para que ele valide o objeto Product, usando novamente uma anotação, desta vez a anotação @Valid. A anotação vai verificar se os campos são validos.

Além da anotação @Valid é necessário adicionar um parâmetro do tipo BindingResult no método. Essa classe é responsável por armazenar o resultado da validação. Após as alterações o método deverá ficar da seguinte maneira:

1
@PostMapping("/products/store")
2
public String store(@Valid @ModelAttribute Product product, BindingResult result) {
3
4
if (result.hasErrors()) {
5
return "create";
6
}
7
8
/*Continua caso não apresente nenhum erro*/
9
10
return "redirect:/products";
11
}

O último detalhe é com relação ao objeto da classe BindingResult que possui o método hasErrors() que verifica se a validação foi executada com sucesso retornando true caso tenha algum tipo de erro e false caso contrário. Com isso é possível restringir a adição do novo produto caso a validação não tenha sido executada com sucesso e retornar para a página de criação do produto enviando um objeto result com as mensagens de erro.

Exibindo as mensagens de erro

O Thymeleaf possui uma forma de exibir as mensagens de erro de validação. Basta usar o atributo th:errors acompanhado do campo que está sendo validado. Por exemplo, para exibir as mensagens de erro do campo title basta usar o atributo th:errors="${product.title}". O mesmo deve ser feito para os outros campos.

1
<form action="/products/" method="post" th:object="${product}">
2
3
<label for="title">Título</label>
4
<input type="text" th:field="${product.title}" placeholder="title" id="title">
5
<div class="error" th:errors="{product.title}">Erro no Título</div>
6
7
<!-- A forma abaixo faz a mesma coisa porém com a escrita diferente -->
8
<label for="price">Preço</label>
9
<input type="number" th:field="*{price}" placeholder="price" id="price" step="any">
10
<div class="error" th:errors="*{price}">Erro no Título</div>
11
12
...
13
14
</form>

Suba a aplicação e acesse a página de criação de um novo produto. Deixe todos os campos vazios e clique em salvar. Perceba que a aplicação não salva o produto e exibe as mensagens de erro. Caso a validação não funcione, tente parar o servidor e fazer um reinicio limpo (usando o comando mvn clean spring-boot:run).

Além da anotação para valição de campo não vazio, o spring possui outras anotações para validação de outros tipos de dados. A seguir é mostrada uma tabela com algumas dessas anotações:

AnotaçãoDescrição
@NotBlankVerifica se o campo não está vazio
@NotNullVerifica se o campo não é nulo
@Size(min, max)Especifica o tamanho ou intervalo de um campo (para coleções, strings, arrays, etc.)
@MinVerifica se o valor do campo é maior ou igual ao valor definido
@MaxVerifica se o valor do campo é menor ou igual ao valor definido
@EmailVerifica se o valor do campo possui o formato de um e-mail válido
@PatternVerifica se o valor do campo possui o formato definido através de uma expressão regular

Validando o campo de preço

O campo de preço é um campo numérico com casas decimais. Logo é preciso verificar se esse campo possui no máximo 2 digitos decimais. Também é possível verificar um valor mínimo para o campo, visto que não faz sentido permitir a entrada de um valor menor do que zero.

Para isso vamos utilizar as anotações @DecimalMin e @Digits. A primeira anotação verifica se o valor do campo é maior ou igual ao valor definido. A segunda anotação verifica se o valor do campo possui o número de dígitos definido. A anotação @Digits possui dois atributos, o atributo integer que define o número de dígitos inteiros e o atributo fraction que define o número de dígitos decimais.

1
public class Product {
2
3
@DecimalMin(value = "0.01", inclusive = true, message = "O preço deve ser maior que 0.00")
4
@Digits(integer = 5, fraction = 2, message = "O preço deve ter no máximo 5 dígitos inteiros e 2 dígitos decimais")
5
private double price;
6
...

Nesse exemplo estamos delimitando um valor máximo de preço que pode ser adicionado mas isso vai depender das regras de negócio da sua própria aplicação.