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.

Mapeamento de tipos da java.time e do 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();
          }
    }
  • Linhas 7 a 9: Obtemos uma instância da sessão a partir da classe utilitária HibernateUtil;
  • Linhas 11 a 29: Definimos o método de inserção de um objeto do tipo Ordem no banco de dados.

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.

Banco de dados após a execução da classe Main
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.