A validação de dados é um aspecto essencial no desenvolvimento de APIs para garantir a consistência e integridade das informações. O Spring oferece suporte às anotações de Bean Validation, como @NotBlank e @NotNull, mas, em alguns casos, precisamos de validações mais complexas. Um exemplo clássico é a necessidade de verificar se um valor já existe no banco de dados antes de persistir um novo registro. Neste post, vamos criar um validador customizado para evitar a duplicação de categorias utilizando a interface Validator do Spring, explicando cada etapa detalhadamente.
  
  
  Passo 1: Criando a Entidade Categoria
A primeira etapa é definir a entidade Categoria, que será persistida no banco de dados. Essa entidade representa uma categoria no sistema e deve ter um nome único.
@Entity
public class Categoria {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @NotBlank
    @Column(nullable = false, unique = true)
    private String nome;
    public Categoria(String nome) {
        Assert.hasText(nome, "O nome é obrigatório");
        this.nome = nome;
    }
    @Deprecated
    public Categoria() { }
    @Override
    public String toString() {
        return "Categoria{" +
                "id=" + id +
                ", nome='" + nome + '\'' +
                '}';
    }
}
Explicação:
- A anotação @Entityindica que esta classe será mapeada para uma tabela no banco de dados.
- 
@Ide@GeneratedValuedefinem a chave primária e sua estratégia de geração.
- 
@Column(nullable = false, unique = true)garante que o nome seja único.
- O construtor principal recebe o nome da categoria e verifica se ele não está vazio.
- Um construtor padrão (deprecated) é mantido para uso do JPA.
  
  
  Passo 2: Criando o DTO NovaCategoriaRequest
Para evitar expor diretamente a entidade no endpoint, criamos um record chamado NovaCategoriaRequest:
public record NovaCategoriaRequest(
        @NotBlank
        String nome
) {
    public Categoria toModel() {
        return new Categoria(this.nome);
    }
}
Explicação:
- 
@NotBlankgarante que o nome da categoria não pode ser vazio ou nulo.
- O método toModel()converte o DTO para a entidadeCategoria.
Passo 3: Criando o Repositório
Precisamos de um repositório para buscar categorias no banco de dados:
public interface CategoriaRepository extends CrudRepository<Categoria, Long> {
    boolean existsByNome(@NotBlank String nome);
}
Explicação:
- 
CrudRepositoryfornece métodos básicos para manipulação de entidades.
- 
existsByNome(String nome)permite verificar se um nome já existe no banco de dados.
Passo 4: Criando o Validador Customizado
Agora criamos a classe ProibeNomeDuplicadoCategoriaValidator, que implementa a interface Validator do Spring para verificar a existência da categoria no banco de dados:
@Component
public class ProibeNomeDuplicadoCategoriaValidator implements Validator {
    private final CategoriaRepository categoriaRepository;
    public ProibeNomeDuplicadoCategoriaValidator(CategoriaRepository categoriaRepository) {
        this.categoriaRepository = categoriaRepository;
    }
    @Override
    public boolean supports(Class<?> clazz) {
        return NovaCategoriaRequest.class.isAssignableFrom(clazz);
    }
    @Override
    public void validate(Object target, Errors errors) {
        if (errors.hasErrors()) {
            return;
        }
        NovaCategoriaRequest request = (NovaCategoriaRequest) target;
        if (categoriaRepository.existsByNome(request.nome())) {
            errors.rejectValue("nome", null, "Já existe uma categoria cadastrada com este nome: " + request.nome());
        }
    }
}
Explicação:
- 
Validatoré uma interface do Spring usada para criar validadores customizados.
- 
supports(Class<?> clazz)define para qual classe esse validador será aplicado.
- 
validate(Object target, Errors errors)verifica se o nome da categoria já existe no banco e rejeita a entrada caso positivo.
- 
errors.hasErrors()verifica se já há erros anteriores, evitando validações desnecessárias.
- 
NovaCategoriaRequest request = (NovaCategoriaRequest) target;faz um cast seguro do objeto recebido.
- 
categoriaRepository.existsByNome(request.nome())consulta o banco para verificar se o nome já foi cadastrado.
- 
errors.rejectValue("nome", null, "Já existe uma categoria cadastrada com este nome: " + request.nome());adiciona um erro ao camponome, impedindo a criação de categorias duplicadas.
Passo 5: Integrando o Validador no Controller
No CategoriasController, registramos o validador customizado no WebDataBinder dentro do método init:
@RestController
public class CategoriasController {
    private final EntityManager entityManager;
    private final ProibeNomeDuplicadoCategoriaValidator proibeNomeDuplicadoCategoriaValidator;
    public CategoriasController(
            final EntityManager entityManager,
            final ProibeNomeDuplicadoCategoriaValidator proibeNomeDuplicadoCategoriaValidator) {
        this.entityManager = entityManager;
        this.proibeNomeDuplicadoCategoriaValidator = proibeNomeDuplicadoCategoriaValidator;
    }
    @InitBinder
    public void init(WebDataBinder binder) {
        binder.addValidators(proibeNomeDuplicadoCategoriaValidator);
    }
    @PostMapping("/categorias")
    @ResponseStatus(HttpStatus.OK)
    @Transactional
    public String criarCategoria(@RequestBody @Valid NovaCategoriaRequest novaCategoriaRequest) {
        Categoria novaCategoria = novaCategoriaRequest.toModel();
        entityManager.persist(novaCategoria);
        return novaCategoria.toString();
    }
}
Explicação:
- 
@InitBindergarante que o validador seja aplicado antes do processamento da requisição.
- 
@Validativa a validação antes de processar oPOST.
- 
entityManager.persist(novaCategoria)salva a nova categoria no banco.
O que está acontecendo aqui?
- 
Método anotado com @InitBinder- O Spring chama métodos marcados com @InitBinderantes de processar requisições para um controlador.
- Esse método permite personalizar o WebDataBinder, que é responsável por converter e validar os dados da requisição antes que eles sejam passados ao método do controlador.
 
- O Spring chama métodos marcados com 
- 
Parâmetro WebDataBinder binder- O WebDataBindergerencia a conversão e validação de objetos que chegam na requisição HTTP.
- Ele pode ser configurado para adicionar conversores personalizados, configurações específicas e validadores.
 
- O 
- 
binder.addValidators(proibeNomeDuplicadoCategoriaValidator);- Esse trecho adiciona o validador ProibeNomeDuplicadoCategoriaValidatoraoWebDataBinder.
- Com isso, toda vez que o NovaCategoriaRequestfor recebido em uma requisição, o Spring automaticamente executará esse validador antes de chamar o método do controlador.
- Se o validador encontrar um erro (como um nome duplicado), ele adicionará esse erro ao contexto da validação, impedindo que a requisição continue.
 
- Esse trecho adiciona o validador 
  
  
  Por que usar @InitBinder?
- Garante que a validação seja aplicada a todas as requisições que manipulam NovaCategoriaRequest, sem precisar chamá-la explicitamente em cada endpoint.
- Mantém o código do controlador mais limpo, pois a validação ocorre antes do método do controlador ser chamado.
- Permite adicionar múltiplos validadores de maneira centralizada.
Em qual contexto esse método é chamado?
Sempre que um POST /categorias for feito, e o @RequestBody @Valid NovaCategoriaRequest for processado, o Spring chamará automaticamente esse @InitBinder, garantindo que o validador personalizado seja executado antes da persistência da entidade.
Passo 6: Testando o Validador
Requisição Válida:
{
    "nome": "Tecnologia"
}
Resposta:
HTTP 200 OK
{
    "id": 1,
    "nome": "Tecnologia"
}
Requisição Duplicada:
{
    "nome": "Tecnologia"
}
Resposta:
HTTP 400 BAD REQUEST
{
    "errors": [
        {
            "field": "nome",
            "message": "Já existe uma categoria cadastrada com este nome: Tecnologia"
        }
    ]
}
Conclusão
Criar validadores customizados no Spring com Validator permite adicionar regras de negócio específicas sem poluir o controlador. Com essa abordagem, garantimos que a lógica de validação fique separada da lógica de controle, tornando o código mais modular e fácil de manter. 
Se você deseja um sistema robusto e confiável, considere usar validadores customizados para garantir a integridade dos seus dados! 🚀
 

 
    
Top comments (0)