Hibernate - Data e hora do mapeamento

Hibernate - Data e hora do mapeamento

1. Introdução

Neste artigo, mostraremos como mapear valores de colunas temporais no Hibernate, incluindo as classes dos pacotesjava.sql,java.utilejava.time.

2. Configuração do Projeto

Para demonstrar o mapeamento dos tipos temporais, vamos precisar do banco de dados H2 e da versão mais recente da bibliotecahibernate-core:


    org.hibernate
    hibernate-core
    5.2.12.Final


    com.h2database
    h2
    1.4.194

Para a versão atual da bibliotecahibernate-core, vá para o repositórioMaven Central.

3. Configuração de fuso horário

When dealing with dates, it’s a good idea to set a specific time zone for the JDBC driver. Desta forma, nosso aplicativo seria independente do fuso horário atual do sistema.

Para nosso exemplo, vamos configurá-lo por sessão:

session = HibernateUtil.getSessionFactory().withOptions()
  .jdbcTimeZone(TimeZone.getTimeZone("UTC"))
  .openSession();

Outra forma seria configurar a propriedadehibernate.jdbc.time_zone no arquivo de propriedades do Hibernate que é usado para construir a fábrica de sessão. Dessa forma, poderíamos especificar o fuso horário uma vez para todo o aplicativo.

4. Tipos de mapeamentojava.sql

O pacotejava.sql contém tipos JDBC que estão alinhados com os tipos definidos pelo padrão SQL:

  • Date corresponde ao tipo SQLDATE, que é apenas uma data sem hora

  • Time corresponde ao tipo SQLTIME, que é uma hora do dia especificada em horas, minutos e segundos

  • Timestamp inclui informações sobre data e hora com precisão de até nanossegundos e corresponde ao tipo SQLTIMESTAMP

Como esses tipos estão alinhados com o SQL, seu mapeamento é relativamente direto. Podemos usar a anotação@Basic ou@Column:

@Entity
public class TemporalValues {

    @Basic
    private java.sql.Date sqlDate;

    @Basic
    private java.sql.Time sqlTime;

    @Basic
    private java.sql.Timestamp sqlTimestamp;

}

Poderíamos então definir os valores correspondentes como este:

temporalValues.setSqlDate(java.sql.Date.valueOf("2017-11-15"));
temporalValues.setSqlTime(java.sql.Time.valueOf("15:30:14"));
temporalValues.setSqlTimestamp(
  java.sql.Timestamp.valueOf("2017-11-15 15:30:14.332"));

Observe que escolher os tiposjava.sql para os campos de entidade nem sempre é uma boa escolha. Essas classes são específicas do JDBC e contêm muitas funcionalidades descontinuadas.

5. Tipo de mapeamentojava.util.Date

The type java.util.Date contains both date and time information, up to millisecond precision. Mas não está diretamente relacionado a nenhum tipo de SQL.

É por isso que precisamos de outra anotação para especificar o tipo SQL desejado:

@Basic
@Temporal(TemporalType.DATE)
private java.util.Date utilDate;

@Basic
@Temporal(TemporalType.TIME)
private java.util.Date utilTime;

@Basic
@Temporal(TemporalType.TIMESTAMP)
private java.util.Date utilTimestamp;

A anotação@Temporal tem o valor de parâmetro único do tipoTemporalType. Pode serDATE,TIME ouTIMESTAMP, dependendo do tipo de SQL subjacente que desejamos para usar no mapeamento.

Poderíamos então definir os campos correspondentes assim:

temporalValues.setUtilDate(
  new SimpleDateFormat("yyyy-MM-dd").parse("2017-11-15"));
temporalValues.setUtilTime(
  new SimpleDateFormat("HH:mm:ss").parse("15:30:14"));
temporalValues.setUtilTimestamp(
  new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
    .parse("2017-11-15 15:30:14.332"));

Como vimos,the java.util.Date type (milliseconds precision) is not precise enough to handle the Timestamp value (nanoseconds precision).

Portanto, quando recuperamos a entidade do banco de dados, sem surpresa encontraremos uma instânciajava.sql.Timestamp neste campo, mesmo se persistirmos inicialmente emjava.util.Date:

temporalValues = session.get(TemporalValues.class,
  temporalValues.getId());
assertThat(temporalValues.getUtilTimestamp())
  .isEqualTo(java.sql.Timestamp.valueOf("2017-11-15 15:30:14.332"));

Isso deve ser adequado para nosso código, já queTimestamp estendeDate.

6. Tipo de mapeamentojava.util.Calendar

Tal como acontece comjava.util.Date, o tipojava.util.Calendar pode ser mapeado para diferentes tipos de SQL, portanto, temos que especificá-los com@Temporal.

A única diferença é que o Hibernate não suporta mapeamento deCalendar paraTIME:

@Basic
@Temporal(TemporalType.DATE)
private java.util.Calendar calendarDate;

@Basic
@Temporal(TemporalType.TIMESTAMP)
private java.util.Calendar calendarTimestamp;

Veja como podemos definir o valor do campo:

Calendar calendarDate = Calendar.getInstance(
  TimeZone.getTimeZone("UTC"));
calendarDate.set(Calendar.YEAR, 2017);
calendarDate.set(Calendar.MONTH, 10);
calendarDate.set(Calendar.DAY_OF_MONTH, 15);
temporalValues.setCalendarDate(calendarDate);

7. Tipos de mapeamentojava.time

Since Java 8, the new Java Date and Time API is available for dealing with temporal values. Esta API corrige muitos dos problemas das classesjava.util.Dateejava.util.Calendar.

Os tipos do pacotejava.time são mapeados diretamente para os tipos SQL correspondentes. Portanto, não há necessidade de especificar explicitamente a anotação@Temporal:

  • LocalDate é mapeado paraDATE

  • LocalTime eOffsetTime são mapeados paraTIME

  • Instant,LocalDateTime,OffsetDateTime eZonedDateTime são mapeados paraTIMESTAMP

Isso significa que podemos marcar esses campos apenas com a anotação@Basic (ou@Column), assim:

@Basic
private java.time.LocalDate localDate;

@Basic
private java.time.LocalTime localTime;

@Basic
private java.time.OffsetTime offsetTime;

@Basic
private java.time.Instant instant;

@Basic
private java.time.LocalDateTime localDateTime;

@Basic
private java.time.OffsetDateTime offsetDateTime;

@Basic
private java.time.ZonedDateTime zonedDateTime;

Cada classe temporal no pacotejava.time tem um métodoparse() estático para analisar o valorString fornecido usando o formato apropriado. Então, aqui está como podemos definir os valores dos campos de entidade:

temporalValues.setLocalDate(LocalDate.parse("2017-11-15"));

temporalValues.setLocalTime(LocalTime.parse("15:30:18"));
temporalValues.setOffsetTime(OffsetTime.parse("08:22:12+01:00"));

temporalValues.setInstant(Instant.parse("2017-11-15T08:22:12Z"));
temporalValues.setLocalDateTime(
  LocalDateTime.parse("2017-11-15T08:22:12"));
temporalValues.setOffsetDateTime(
  OffsetDateTime.parse("2017-11-15T08:22:12+01:00"));
temporalValues.setZonedDateTime(
  ZonedDateTime.parse("2017-11-15T08:22:12+01:00[Europe/Paris]"));

8. Conclusão

Neste artigo, mostramos como mapear valores temporais de diferentes tipos no Hibernate.

O código-fonte do artigo está disponívelover on GitHub.