Hibernate: Persistindo datas no Java sem incompatibilidade de tipos

Veja neste artigo como utilizar a API de datas do Java 8 com o Hibernate sem problemas de mapeamento.

Motivação

Com o lançamento da API Date and Time, cujo objetivo principal é simplificar a manipulação de datas, um pequeno problema surgiu: os novos tipos de dados não eram completamente suportados pela especificação da JPA 2.1. Dessa forma, classes como LocalDate podem ser utilizadas como atributo de uma entidade, mas devido à incompatibilidade, não é possível anotar esses atributos com @Temporal e armazená-los no banco de dados.

Como o lançamento do JPA 2.2 ainda não ocorreu (até o momento da publicação desse artigo), uma das implementações da JPA criou uma solução própria. O Hibernate 5 tornou possível o mapeamento de classes da API de datas do Java 8 com os tipos JDBC. A Figura 1 mostra o mapeamento criado entre os novos tipos do pacote java.time e os tipos JDBC.

Figura 1. Mapeamento de tipos da java.time e do JDBC

Nesse artigo, veremos como utilizar a API de datas do Java 8 juntamente com o Hibernate 5.

Passo 1: Criar o projeto

Para o exemplo que desenvolveremos aqui, vamos utilizar o MySQL 5.7.11, o NetBeans 8.2 e, com eles, criaremos uma aplicação exemplo simples, contendo apenas uma entidade, chamada Ordem, cujo script SQL para criação no banco de dados é apresentado na Listagem 1.

Listagem 1. Script para criação da tabela Ordem

CREATE TABLE ordem (
 ordemId bigint(20) NOT NULL AUTO_INCREMENT,
 OrdemProduct varchar(255) DEFAULT NULL,
 ordemCreated timestamp NULL DEFAULT NULL,
 ordemDate date DEFAULT NULL,
 ordemTime time DEFAULT NULL,
 ordemStart timestamp,
 ordemEnd timestamp,
 ordemTimeExecuted bigint(20), 
 ordemTimeZonedTime timestamp,
 PRIMARY KEY (ordemId) );

Após criar o banco de dados, crie um novo projeto Java, chamado hibernate-time, e adicione as bibliotecas referentes ao Hibernate Core em seu classpath. A versão do Hibernate que faremos uso é a 5.2.6.Final, a mais recente no momento da escrita desse material.

Passo 2: Mapear as classes do domínio da aplicação

Para definir o mapeamento objeto/relacional da entidade do domínio da aplicação, adicione ao projeto uma nova classe chamada Ordem, cujo código pode ser visto na Listagem 2.

Listagem 2. Código da classe Ordem

 //imports omitidos   

 @Entity
 @Table(name = "ordem")
 public class Ordem {
     
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long ordemId;

    private String OrdemProduct;
    private LocalDate ordemDate;
    private LocalTime ordemTime;
    private LocalDateTime ordemCreated;
    private ZonedDateTime ordemTimeZonedTime;
    private Instant ordemStart;
    private Instant ordemEnd;
    private Duration ordemTimeExecuted;

   //construtores e métodos gets e sets omitidos  
 } 
  

Nesse exemplo, fizemos uso dos novos tipos da API Java Time para definir os atributos da classe. LocalTime, LocalDate e LocalDateTime, provavelmente, são as classes mais utilizadas ao lidar com essa API, e representam, respectivamente, uma data, um horário e os dois juntos. A classe ZonedDateTime representa uma data e hora em um fuso horário específico. Já a classe Instant, diz respeito a um ponto no tempo com a precisão de nanosegundos. A classe Duration, por sua vez, geralmente utiliza internamente a classe anterior e serve para medir um intervalo de tempo em nanosegundos.

As anotações presentes nessa classe são: @Entity e @Table, que indicam que os objetos dessa classe serão persistidos no banco de dados; além de @GeneratedValue e @Id, para criação de uma chave primária para o objeto.

O destaque aqui fica para a ausência da anotação @Temporal nos demais atributos. O Hibernate 5 tem informações suficientes para inferir o mapeamento correto da propriedade dentro do banco. Com isso, ele lê, por exemplo, o tipo LocalDateTime e, corretamente, insere o dado no banco de dados como um timestamp.

Passo 3: Configurar o acesso ao banco de dados

Agora, é necessário informar ao Hibernate onde está o banco de dados e as informações necessárias para se conectar a ele. Portanto, crie um arquivo XML de nome hibernate.cfg, na pasta src do projeto, e defina nele as configurações comumente utilizadas.

O passo seguinte é codificar a classe utilitária HibernateUtil, que fará a ligação entre o arquivo XML e o banco de dados, disponibilizando uma instância de SessionFactory para obter um objeto de sessão. A Listagem 3 mostra a implementação dessa classe.

Listagem 3. Código da classe HibernateUtil

 //imports omitidos

 public class HibernateUtil {

  private static SessionFactory sessionFactory;

  public static SessionFactory getSessionFactory() {
    if (sessionFactory == null) {         
       Configuration configuration = new Configuration().configure();           
       sessionFactory = configuration.buildSessionFactory();
     }
    return sessionFactory;
   }
  }
  

Passo 4: Criar a classe DAO

Com a classe de persistência mapeada com as anotações do Hibernate, o arquivo de configuração e a conexão com o banco de dados implementados, partiremos para a criação do DAO, OrdemDAO, responsável por disponibilizar as operações de persistência. Seu código pode ser visto na Listagem 4.

Listagem 4. Código da classe OrdemDAO

 //imports omitidos...

 public class OrdemDAO { 

     private static SessionFactory factory; 

     public OrdemDAO() { 
           factory = HibernateUtil.getSessionFactory();  
     } 

     public void addOrdem(Ordem ordem) { 
           Session session = factory.openSession(); 
           Transaction tx = null;
           Integer cod_aluno = null; 

           try { 
                  tx = session.beginTransaction(); 
                  session.save(ordem); 
                  tx.commit();
           } 
           catch (HibernateException e) { 
                  if (tx != null) {
                         tx.rollback(); 
                  } 
                  e.printStackTrace(); 
           } finally { 
                  session.close(); 
           } 
     }
  

Passo 5: Testar a aplicação

Por fim, chega o momento de testar a aplicação. A execução da classe Main, apresentada na Listagem 5, permite visualizar as inserções no banco de dados.

Na linha 5 é criado um objeto do tipo Ordem e em seguida seus atributos são preenchidos. Esse objeto é persistido no banco de dados através da chamada ao método addOrdem(), da classe OrdemDAO.

Listagem 5. Implementação da classe de teste, Main

 //imports omitidos…

 public class Main {
    public static void main(String[] args) {       
            Instant inicio = Instant.now();
            Ordem ordem1=new Ordem();
            ordem1.setOrderProduct("Internet Link");
            ordem1.setOrderDate(LocalDate.now());
            ordem1.setOrdemTime(LocalTime.now());
            
            ZoneId fusoHorario = ZoneId.of("America/Sao_Paulo");
            ZonedDateTime agoraEmSaoPaulo = ZonedDateTime.now(fusoHorario);
            ordem1.setOrdemTimeZonedTime(agoraEmSaoPaulo);
            ordem1.setOrderCreated(LocalDateTime.now());  
            ordem1.setOrdemStart(inicio);
            Instant fim = Instant.now();
            ordem1.setOrdemStart(fim);
            ordem1.setOrdemTimeExecuted(Duration.between(inicio, fim));
            OrdemDAO ord = new OrdemDAO();
            ord.addOrdem(ordem1);
     }
 }
  

A Figura 2 mostra a visão do banco de dados após a execução da classe de teste. Veja que os valores definidos foram inseridos corretamente no banco de dados.

Figura 2. Banco de dados após a execução da classe Main

O Hibernate 5, como vimos, simplificou o trabalho com as classes da API de datas do Java 8, evitando a necessidade de efetuar várias conversões a fim de manter a compatibilidade entre os tipos de dados dos objetos e do banco.

Artigos relacionados