Ao trabalhar com validações em Spring Boot, muitas vezes precisamos garantir que certos campos sejam únicos no banco de dados. No entanto, criar validadores individuais para cada entidade pode resultar em código repetitivo e de difícil manutenção. Neste post, vamos explorar como criar um validador genérico reutilizável para qualquer campo que precise ser único.
O Problema
Temos duas entidades no sistema:
-
Categoria
, onde o camponome
deve ser único. -
Autor
, onde o campoemail
deve ser único.
Ao invés de criar validadores separados para cada caso, podemos construir um validador genérico que possa ser reutilizado para qualquer entidade.
Criando o Validador Genérico
1. Implementação do Validador
Criamos a classe CampoUnicoValidator<T>
, que verifica se um campo já existe no banco de dados:
public class CampoUnicoValidator<T> implements Validator {
private final Function<T, String> campoExtractor;
private final Function<String, Boolean> existeNoBanco;
private final String campoNome;
private final Class<T> targetClass;
public CampoUnicoValidator(Function<T, String> campoExtractor,
Function<String, Boolean> existeNoBanco,
String campoNome,
Class<T> targetClass) {
this.campoExtractor = campoExtractor;
this.existeNoBanco = existeNoBanco;
this.campoNome = campoNome;
this.targetClass = targetClass;
}
@Override
public boolean supports(Class<?> clazz) {
return targetClass.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
if (errors.hasErrors()) {
return;
}
@SuppressWarnings("unchecked")
T request = (T) target;
String valor = campoExtractor.apply(request);
if (existeNoBanco.apply(valor)) {
errors.rejectValue(campoNome, null, "Já existe um registro com este " + campoNome + ": " + valor);
}
}
}
Como funciona?
-
campoExtractor
: Função que extrai o valor do campo a ser validado. -
existeNoBanco
: Função que verifica no repositório se o valor já existe. -
campoNome
: Nome do campo para exibir mensagens de erro. -
targetClass
: Classe da requisição associada ao validador.
Dessa forma, podemos reusar essa lógica para qualquer entidade!
2. Criando Beans para os Validadores
Agora, criamos os beans para injetar os validadores nos controladores.
@Configuration
public class ValidadoresConfig {
@Bean
public CampoUnicoValidator<NovoAutorRequest> emailUnicoValidator(AutorRepository autorRepository) {
return new CampoUnicoValidator<>(
NovoAutorRequest::email,
autorRepository::existsByEmail,
"email",
NovoAutorRequest.class
);
}
@Bean
public CampoUnicoValidator<NovaCategoriaRequest> nomeUnicoValidator(CategoriaRepository categoriaRepository) {
return new CampoUnicoValidator<>(
NovaCategoriaRequest::nome,
categoriaRepository::existsByNome,
"nome",
NovaCategoriaRequest.class
);
}
}
Isso permite injetar validadores personalizados em diferentes controladores.
3. Usando o Validador no CategoriasController
@RestController
public class CategoriasController {
private final EntityManager entityManager;
private final CampoUnicoValidator<NovaCategoriaRequest> nomeUnicoCategoriaValidator;
public CategoriasController(
final EntityManager entityManager,
final CampoUnicoValidator<NovaCategoriaRequest> nomeUnicoCategoriaValidator) {
this.entityManager = entityManager;
this.nomeUnicoCategoriaValidator = nomeUnicoCategoriaValidator;
}
@InitBinder
public void init(WebDataBinder binder) {
binder.addValidators(nomeUnicoCategoriaValidator);
}
@PostMapping("/categorias")
@ResponseStatus(HttpStatus.OK)
@Transactional
public String criarCategoria(@RequestBody @Valid NovaCategoriaRequest novaCategoriaRequest) {
Categoria novaCategoria = novaCategoriaRequest.toModel();
entityManager.persist(novaCategoria);
return novaCategoria.toString();
}
}
O método @InitBinder
registra o validador para ser executado antes da requisição ser processada.
4. Usando o Validador no AutoresController
@RestController
public class AutoresController {
private final EntityManager entityManager;
private final CampoUnicoValidator<NovoAutorRequest> emailUnicoAutorValidator;
public AutoresController(
final EntityManager entityManager,
final CampoUnicoValidator<NovoAutorRequest> emailUnicoAutorValidator) {
this.entityManager = entityManager;
this.emailUnicoAutorValidator = emailUnicoAutorValidator;
}
@InitBinder
public void init(WebDataBinder binder) {
binder.addValidators(emailUnicoAutorValidator);
}
@PostMapping(value = "/autores")
@Transactional
@ResponseStatus(HttpStatus.OK)
public String criaAutor(@RequestBody @Valid NovoAutorRequest novoAutorRequest) {
Autor novoAutor = novoAutorRequest.toModel();
entityManager.persist(novoAutor);
return novoAutor.toString();
}
}
Conclusão
Com essa abordagem, conseguimos:
✅ Criar um validador genérico e reutilizável.
✅ Evitar código repetitivo em controladores.
✅ Registrar os validadores automaticamente via @Bean
.
✅ Validar qualquer campo único em diferentes entidades sem criar classes separadas para cada caso.
Essa é uma solução elegante e escalável para validações de unicidade no Spring Boot! 🚀
Top comments (0)