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.
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.
Artigos relacionados
-
Artigo
-
Artigo
-
Artigo
-
Artigo
-
Vídeo