DEV Community

Uiratan Cavalcante
Uiratan Cavalcante

Posted on

Como criar uma Annotation que realize validação personalizada para garantir valores únicos no banco de dados com Java

Se você já desenvolveu aplicações Java, provavelmente já se deparou com a necessidade de garantir que um determinado valor seja único no banco de dados. Por exemplo, ao cadastrar um usuário, você pode querer garantir que o e-mail ou nome de usuário não esteja duplicado. Neste post, vou te mostrar como criar uma validação personalizada em Java para garantir que um campo seja único no banco de dados, usando anotações e o framework de validação do Bean Validation (JSR 380).

Vamos seguir um passo a passo simples e prático para implementar essa solução.


Passo 1: Criar a Anotação Personalizada

A primeira etapa é criar uma anotação personalizada que será usada para marcar os campos que devem ser validados como únicos.

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Constraint(validatedBy = UniqueValueValidator.class) // Define a classe que implementa a validação
@Target({ ElementType.FIELD }) // A anotação pode ser usada apenas em campos
@Retention(RetentionPolicy.RUNTIME) // A anotação estará disponível em tempo de execução
public @interface UniqueValue {
    String message() default "Este valor já existe no banco de dados"; // Mensagem padrão de erro

    Class<?>[] groups() default {}; // Grupos de validação (opcional)

    Class<? extends Payload>[] payload() default {}; // Payload para metadados (opcional)

    String fieldName(); // Nome do campo que será validado

    Class<?> domainClass(); // Classe da entidade que contém o campo
}
Enter fullscreen mode Exit fullscreen mode

O que essa anotação faz?

  • @Constraint: Define que a anotação será validada pela classe UniqueValueValidator.
  • @Target: Restringe o uso da anotação a campos (ElementType.FIELD).
  • @Retention: Garante que a anotação esteja disponível em tempo de execução.
  • message(): Define a mensagem de erro padrão.
  • fieldName() e domainClass(): Parâmetros obrigatórios que indicam o nome do campo e a classe da entidade que será validada.

Passo 2: Implementar a Lógica de Validação

Agora, vamos implementar a classe que contém a lógica de validação. Essa classe deve implementar a interface ConstraintValidator.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManager;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.List;

public class UniqueValueValidator implements ConstraintValidator<UniqueValue, String> {

    private String fieldName; // Nome do campo a ser validado
    private Class<?> domainClass; // Classe da entidade

    @Autowired
    private EntityManager entityManager; // EntityManager para consultas no banco de dados

    @Override
    public void initialize(UniqueValue constraintAnnotation) {
        // Inicializa os valores do campo e da classe de domínio
        this.fieldName = constraintAnnotation.fieldName();
        this.domainClass = constraintAnnotation.domainClass();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // Se o valor for vazio, considera válido
        if (StringUtils.isEmpty(value)) {
            return true;
        }

        // Cria a consulta SQL para verificar se o valor já existe
        String query = String.format("SELECT 1 FROM %s WHERE %s = :value", domainClass.getSimpleName(), fieldName);

        // Executa a consulta
        List<?> result = entityManager.createQuery(query)
                .setParameter("value", value)
                .getResultList();

        // Se a lista estiver vazia, o valor é único
        return result.isEmpty();
    }
}
Enter fullscreen mode Exit fullscreen mode

O que essa classe faz?

  • initialize(): Inicializa os valores do campo e da classe de domínio a partir da anotação.
  • isValid(): Verifica se o valor já existe no banco de dados. Se o valor for único, retorna true; caso contrário, retorna false.
  • EntityManager: Usado para realizar consultas no banco de dados.

Passo 3: Usar a Anotação na Entidade

Agora que temos a anotação e o validador, podemos usá-los em uma entidade JPA. Por exemplo, para garantir que o campo email na entidade User seja único:

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @UniqueValue(fieldName = "email", domainClass = User.class, message = "Este e-mail já está em uso")
    private String email;

    // Outros campos, getters e setters
}
Enter fullscreen mode Exit fullscreen mode

O que isso faz?

  • A anotação @UniqueValue é aplicada ao campo email.
  • Quando um novo usuário for cadastrado, o validador verificará se o e-mail já existe no banco de dados.
  • Se o e-mail já existir, a validação falhará e a mensagem "Este e-mail já está em uso" será exibida.

Passo 4: Testar a Validação

Para testar a validação, você pode criar um controlador REST que recebe um objeto User e valida os dados antes de salvar no banco de dados.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@RestController
public class UserController {

    @Autowired
    private UserRepository userRepository;

    @PostMapping("/users")
    public String createUser(@Valid @RequestBody User user) {
        userRepository.save(user);
        return "Usuário cadastrado com sucesso!";
    }
}
Enter fullscreen mode Exit fullscreen mode

O que isso faz?

  • O método createUser recebe um objeto User no corpo da requisição.
  • A anotação @Valid ativa a validação automática do Bean Validation.
  • Se o e-mail já existir no banco de dados, uma exceção de validação será lançada e a mensagem de erro será retornada.

Conclusão

Criar uma validação personalizada para garantir valores únicos no banco de dados é uma tarefa comum no desenvolvimento de aplicações Java. Com a abordagem apresentada, você pode criar uma solução reutilizável e flexível, aplicando a validação de forma declarativa em suas entidades.

Essa técnica pode ser estendida para validar outros tipos de campos e regras de negócio, tornando seu código mais robusto e seguro. Experimente implementar essa solução em seus projetos e veja como ela pode simplificar a validação de dados!

Se tiver dúvidas ou sugestões, deixe um comentário abaixo. Até a próxima! 🚀


Dicas Extras:

  • Certifique-se de que o EntityManager esteja corretamente configurado no seu projeto Spring.
  • Use mensagens de erro personalizadas para melhorar a experiência do usuário.
  • Teste a validação em diferentes cenários para garantir que ela funcione como esperado.

Espero que este post tenha sido útil! 😊

Top comments (0)