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.
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.
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.
//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.
//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.
//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.
//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.
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.