Hibernate: asignación de fecha y hora

1. Introducción

En este artículo, mostraremos cómo mapear valores de columnas temporales en Hibernate, incluidas las clases de los paquetes java.sql , java.util y java.time .

2. Configuración del proyecto

Para demostrar el mapeo de los tipos temporales, necesitaremos la base de datos H2 y la última versión de la biblioteca hibernate-core :

 org.hibernate hibernate-core 5.4.12.Final   com.h2database h2 1.4.194 

Para obtener la versión actual de la biblioteca hibernate-core , diríjase al repositorio de Maven Central.

3. Configuración de la zona horaria

Cuando se trata de fechas, es una buena idea establecer una zona horaria específica para el controlador JDBC. De esta manera nuestra aplicación sería independiente de la zona horaria actual del sistema.

Para nuestro ejemplo, lo configuraremos por sesión:

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

Otra forma sería configurar la propiedad hibernate.jdbc.time_zone en el archivo de propiedades de Hibernate que se usa para construir la fábrica de sesiones. De esta manera, podríamos especificar la zona horaria una vez para toda la aplicación.

4. Asignación de tipos de java.sql

El paquete java.sql contiene tipos JDBC que están alineados con los tipos definidos por el estándar SQL:

  • La fecha corresponde al tipo DATE SQL, que es solo una fecha sin hora
  • La hora corresponde al tipo TIME SQL, que es una hora del día especificada en horas, minutos y segundos.
  • La marca de tiempo incluye información sobre la fecha y la hora con precisión de hasta nanosegundos y corresponde al tipo SQL TIMESTAMP

Como estos tipos están en línea con SQL, su mapeo es relativamente sencillo. Podemos usar la anotación @Basic o @Column :

@Entity public class TemporalValues { @Basic private java.sql.Date sqlDate; @Basic private java.sql.Time sqlTime; @Basic private java.sql.Timestamp sqlTimestamp; }

Entonces podríamos establecer los valores correspondientes 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"));

Tenga en cuenta que la elección de tipos java.sql para campos de entidad puede no ser siempre una buena opción. Estas clases son específicas de JDBC y contienen muchas funciones obsoletas.

5. Asignación del tipo java.util.Date

El tipo java.util.Date contiene información de fecha y hora, con una precisión de milisegundos. Pero no se relaciona directamente con ningún tipo de SQL.

Es por eso que necesitamos otra anotación para especificar el tipo de SQL deseado:

@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;

La anotación @Temporal tiene el valor de parámetro único de tipo TemporalType. Puede ser DATE , TIME o TIMESTAMP , dependiendo del tipo de SQL subyacente que queramos usar para el mapeo.

A continuación, podríamos establecer los campos correspondientes de esta manera:

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 hemos visto, el tipo java.util.Date (precisión de milisegundos) no es lo suficientemente preciso para manejar el valor de marca de tiempo (precisión de nanosegundos).

Entonces, cuando recuperamos la entidad de la base de datos, como era de esperar, encontraremos una instancia de java.sql.Timestamp en este campo, incluso si inicialmente persistimos un java.util.Date :

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

Esto debería estar bien para nuestro código, ya que la marca de tiempo extiende la fecha .

6. Asignación del tipo java.util.Calendar

Al igual que con java.util.Date , el tipo java.util.Calendar se puede asignar a diferentes tipos de SQL, por lo que tenemos que especificarlos con @Temporal .

La única diferencia es que Hibernate no admite la asignación de Calendar a TIME :

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

Así es como podemos establecer el valor del 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. Asignación de tipos de java.time

Desde Java 8, la nueva API de fecha y hora de Java está disponible para tratar con valores temporales . Esta API soluciona muchos de los problemas de las clases java.util.Date y java.util.Calendar .

Los tipos del paquete java.time se asignan directamente a los tipos de SQL correspondientes. Por lo tanto, no es necesario especificar explícitamente la anotación @Temporal :

  • LocalDate está asignado a DATE
  • LocalTime y OffsetTime se asignan a TIME
  • Instant , LocalDateTime , OffsetDateTime y ZonedDateTime se asignan a TIMESTAMP

Esto significa que podemos marcar estos campos solo con la anotación @Basic (o @Column ), como esta:

@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 clase temporal en el paquete java.time tiene un método parse () estático para analizar el valor de String proporcionado usando el formato apropiado. Entonces, así es como podemos establecer los valores de los campos de la entidad:

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. Conclusión

En este artículo, hemos mostrado cómo mapear valores temporales de diferentes tipos en Hibernate.

El código fuente del artículo está disponible en GitHub.