A tarefa de validar dados é extremamente comum e importante nos sistemas atuais. Seja em virtude de inputs realizados por usuários ou mesmo devido a informações oriundas de outros sistemas, a validação permite um maior controle sobre os dados, evitando problemas de integridade ou corrupção, falhas de segurança e consistência das informações.

O Hibernate Validator, implementação de referência da especificação Bean Validation, possibilita realizar as validações dos dados diretamente nas classes de domínio da aplicação, permitindo que as informações cheguem às regras de negócio e aos campos de persistência com o conteúdo esperado.

Essa opção faz com que a validação se concentre em apenas uma camada e seja realizada de forma simples e rápida, evitando a perda de tempo durante o desenvolvimento ao fazer uso apenas de metadados (anotações) no código.

Criar o Projeto

O primeiro passo aqui será criar um projeto Maven no Eclipse. Feito isso, podemos adicionar a dependência referente ao Hibernate Validator no arquivo pom.xml. Esse framework também requer a implementação da Unified Expression Language (EL) para avaliar expressões dinâmicas em mensagens de violação de restrições. Quando a aplicação é executada em um container Java EE como o WildFly, uma implementação EL já é fornecida. Em um ambiente Java SE, no entanto, como no nosso caso, é necessário adicionar uma implementação dessa biblioteca.

A Listagem 1 mostra as dependências adicionadas ao arquivo de configuração do Maven. Essas serão as únicas dependências adicionadas ao nosso projeto. Como o foco desse artigo é abordar o processo de validação, não serão mencionados detalhes referentes ao mapeamento objeto relacional; portanto, não serão adicionadas as bibliotecas do Hibernate Core, assim como não adicionaremos a biblioteca referente ao driver do banco, pois, por questão de simplicidade, não utilizaremos um banco de dados. Aqui, é válido ressaltar apenas que utilizar recursos do Hibernate Validator não influencia no processo de mapeamento feito com anotações do framework ORM.


<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.0.Final</version>
</dependency>
<dependency>
  <groupId>org.glassfish.web</groupId>
<artifactId>el-impl</artifactId>
  <version>2.2</version>
</dependency>
Listagem 1. Dependências necessárias para o Hibernate Validator

Criar o Modelo

O sistema conterá apenas uma entidade, Vinho, com os seguintes atributos: código, nome, safra, volume, preço, tipo, data de engarrafamento, lote, origem e descrição. Seu código pode ser visto na Listagem 2.

Como pode ser observado, essa classe é um JavaBeans que possui apenas atributos e seus respectivos getters e setters, além das restrições do Hibernate Validator, adicionadas sobre as propriedades. Note, ainda, que não há lógica alguma nessa classe. Sua única função é representar o domínio da aplicação.


public class Vinho { 

public enum TipoVinho {
	ROSE,
 TINTO,
	BRANCO
}

public Vinho(int codigo, String nome, Year safra, int volume, 
BigDecimal preco, 10     String lote, String descricao, 
Instant data_engarrafamento, String origem){
         this.codigo = codigo;
				     this.nome = nome;
         this.safra = safra;
         this.volume = volume;
         this.preco = preco;
         this.lote = lote;
         this.tipo = TipoVinho.BRANCO;
         this.descricao  = descricao;
         this.data_engarrafamento = data_engarrafamento;
         this.origem = origem;
  }

@NotNull(message = "Codigo é obrigatório")
   private int codigo;

   @NotEmpty(message = "Nome do vinho é obrigatório")
private String nome;

   @NotNull(message = "Safra do vinho é obrigatório")
   private Year safra;
		
   @NotNull(message = "Volume é obrigatório em mL")
   @Range(min=30,max=2000,message="A qde de 
   liquido varia entre 30ml e 2000ml")
   private Integer volume;

@Digits(integer=3,fraction=2,message="Apenas 
centenas e 2 casas após o ponto.")
private BigDecimal preco;

@NotNull
	private TipoVinho tipo;

	@Past(message = "A data deve estar no passado.")
private Instant data_engarrafamento;

@NotEmpty(message = "Número do lote deve ser informado.")
@Pattern(regexp = "\\d\\p{Upper}{2}\\d\\p{Upper}", message = 
"Padrão do lote deve ser obedecido.")
private String lote;

@Size(min=4,max=20,message="Descrição deve ter no máximo {max} 
caracteres e no minimo" + " {min} caracteres. Você digitou: " 
+ "${validatedValue}")
private String descricao;

@NotEmpty (message = "Informe um valor para a origem do Vinho.")
private String origem;
} //getters e setters omitidos
Listagem 2. Código da classe Vinho

Linhas 26, 45 e 52: A anotação @NotEmpty especifica que o campo que a recebeu não pode ser vazio e nem nulo;

Linhas 23, 29, 32 e 39: A anotação @NotNull valida se o campo não é nulo;

Linha 36: A anotação @Digits(integer=3,fraction=2) verifica se o preço atribuído ao vinho é um número com até três inteiros e no máximo dois dígitos na parte fracionária;

Linha 49: A anotação @Size verifica se o texto informado para a descrição tem a quantidade de caracteres compreendida entre 4 (atributo min) e 20 (atributo max). O atributo message, por sua vez, possibilita a personalização da mensagem de erro. Note, também, que concatenamos o valor informado à mensagem de erro, por meio da variável validateValue, recuperada via Expression Language;

Linha 33: A anotação @Range especifica que o volume de líquido da garrafa de vinho deve estar compreendido entre 30ml e 2000ml;

Linha 46: A anotação @Pattern indica que o valor informado para o lote deve corresponder à expressão regular registrada na propriedade regexp. Essa expressão define que o valor referente ao lote deve ser composto por sequências alfanuméricas, sendo que o primeiro e o quarto caracteres correspondem a dígitos e os demais, a letras maiúsculas;

Linha 42: A anotação @Past é inserida para checagens referentes a datas. Nesse caso, ela aparece em uma variável do tipo Instant, disponibilizada a partir da nova API de datas do Java 8. Esse suporte a esse novo tipo foi disponibilizado a partir da versão 5.2 do Hibernate. Outros tipos da nova API, como ChronoZonedDateTime e java.time.OffsetDateTime, também podem passar por essa validação.

Neste ponto, é válido citar que existe uma discussão em torno dos tipos LocalDate e LocalTime, ou qualquer outro que não representa um instante no tempo, sobre o emprego das anotações @Past e @Future. Essas classes não armazenam ou representam uma hora ou fuso horário. Trata-se da descrição de uma data, e como não representam um instante na linha do tempo, não podem passar por esse tipo de validação, pois passado e futuro ficam indefinidos.

Por esse motivo, até o momento esse tipo de suporte não foi disponibilizado para esses tipos. Discussões estão sendo realizadas e é possível que nas próximas versões do Hibernate Validator, ou até mesmo no lançamento da nova versão da especificação Bean Validation, esse tipo de suporte esteja disponível.

Testar a aplicação

Por fim, para verificar o funcionamento das validações, criaremos uma classe de teste chamada VinhoTeste. O código desta classe pode ser visto na Listagem 3.


//imports omitidos...
public class VinhoTest {
  public static void main(String[] args) {
           
    Vinho vinho = new Vinho(1, "Portal do Fidalgo",    
    Year.of(2012), 2045, new BigDecimal("13.934"), "4AZ6F", 
    "Vin", Instant.MAX, "Chile");   
  
  ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
  Validator validator = factory.getValidator ();
  
  Set<ConstraintViolation<Vinho>> constraintViolations =
  validator.validate( vinho);
  
  for (ConstraintViolation error: constraintViolations) { 
      String msgError = error.getMessage(); 
      System.out.println (msgError);
  }
}
} 
Listagem 3. Implementação da classe de teste

Linha 5: Um objeto do tipo Vinho é criado e nele são inseridos os valores dos atributos que serão validados;

Linhas 7 e 8: Para realizar a validação das restrições, usaremos uma instância da classe Validator, recuperada a partir de um ValidatorFactory;

Linha 10: O método validate() recebe o objeto que será validado e retorna um array de violações de restrições do tipo ConstraintViolation;

Linha 11: O array recuperado é percorrido em ordem para ver quais erros de validação ocorreram. Caso nenhum erro de validação aconteça, o método retorna um conjunto vazio.

Nesse artigo utilizamos uma instância da classe Validator diretamente por questão de simplicidade. Tecnologias como JSF e JPA oferecem integração com o Hibernate Validator. Assim, basta anotar os JavaBeans com as restrições e a validação ocorrerá automaticamente na fase de ciclo de vida pertinente da tecnologia utilizada.

A Figura 1 mostra o resultado da execução da classe de teste, com as mensagens impressas.

Resultado da execução da classe de Teste
Figura 1. Resultado da execução da classe de Teste

Uma das grandes vantagens dessa API refere-se ao fato da validação ser centralizada em uma só camada, a camada de domínio, sendo implementada apenas uma vez. Com isso, evita-se a reescrita de código em virtude de uma nova implementação de validações, tornando o código menos verboso e mais legível. Além disso, essa técnica não está associada a um modelo de programação específico, podendo ser utilizada, por exemplo, em projetos web e desktop.