localdatetime java hibernate datetime jpa timezone

java - localdatetime - Cómo almacenar la fecha/hora y las marcas de tiempo en la zona horaria UTC con JPA e Hibernate



jpa localtime (10)

¿Cómo puedo configurar JPA / Hibernate para almacenar una fecha / hora en la base de datos como zona horaria UTC (GMT)? Considere esta entidad anotada de JPA:

public class Event { @Id public int id; @Temporal(TemporalType.TIMESTAMP) public java.util.Date date; }

Si la fecha es 2008-feb-03 a las 9:30 a.m. hora del Pacífico (PST), entonces quiero que la hora UTC de 2008-feb-03 a las 5:30 p.m. se almacene en la base de datos. Del mismo modo, cuando la fecha se recupera de la base de datos, quiero que se interprete como UTC. Entonces, en este caso, 530 p.m. son las 5:30 p.m. UTC. Cuando se muestre, se formateará como 9:30 a.m. PST.


Agregando una respuesta que está completamente basada y en deuda con Divestoclimb con una pista de Shaun Stone. Solo quería deletrearlo en detalle ya que es un problema común y la solución es un poco confusa.

Esto está usando Hibernate 4.1.4.Final, aunque sospecho que cualquier cosa después de 3.6 funcionará.

Primero, crea el UtcTimestampTypeDescriptor de divestoclimb

public class UtcTimestampTypeDescriptor extends TimestampTypeDescriptor { public static final UtcTimestampTypeDescriptor INSTANCE = new UtcTimestampTypeDescriptor(); private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); public <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) { return new BasicBinder<X>( javaTypeDescriptor, this ) { @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { st.setTimestamp( index, javaTypeDescriptor.unwrap( value, Timestamp.class, options ), Calendar.getInstance(UTC) ); } }; } public <X> ValueExtractor<X> getExtractor(final JavaTypeDescriptor<X> javaTypeDescriptor) { return new BasicExtractor<X>( javaTypeDescriptor, this ) { @Override protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return javaTypeDescriptor.wrap( rs.getTimestamp( name, Calendar.getInstance(UTC) ), options ); } }; } }

A continuación, cree UtcTimestampType, que utiliza UtcTimestampTypeDescriptor en lugar de TimestampTypeDescriptor como SqlTypeDescriptor en la llamada de superconstructor, pero de lo contrario delega todo en TimestampType:

public class UtcTimestampType extends AbstractSingleColumnStandardBasicType<Date> implements VersionType<Date>, LiteralType<Date> { public static final UtcTimestampType INSTANCE = new UtcTimestampType(); public UtcTimestampType() { super( UtcTimestampTypeDescriptor.INSTANCE, JdbcTimestampTypeDescriptor.INSTANCE ); } public String getName() { return TimestampType.INSTANCE.getName(); } @Override public String[] getRegistrationKeys() { return TimestampType.INSTANCE.getRegistrationKeys(); } public Date next(Date current, SessionImplementor session) { return TimestampType.INSTANCE.next(current, session); } public Date seed(SessionImplementor session) { return TimestampType.INSTANCE.seed(session); } public Comparator<Date> getComparator() { return TimestampType.INSTANCE.getComparator(); } public String objectToSQLString(Date value, Dialect dialect) throws Exception { return TimestampType.INSTANCE.objectToSQLString(value, dialect); } public Date fromStringValue(String xml) throws HibernateException { return TimestampType.INSTANCE.fromStringValue(xml); } }

Finalmente, cuando inicialice su configuración de Hibernate, registre UtcTimestampType como una anulación de tipo:

configuration.registerTypeOverride(new UtcTimestampType());

Ahora las marcas de tiempo no deberían preocuparse por la zona horaria de la JVM en su camino hacia y desde la base de datos. HTH.


Con Hibernate 5.2, ahora puede forzar la zona horaria UTC utilizando la siguiente propiedad de configuración:

<property name="hibernate.jdbc.time_zone" value="UTC"/>

Para más detalles, mira este artículo .


Encontré el mismo problema cuando quería almacenar las fechas en el DB como UTC y evitar el uso de varchar y las conversiones String <-> java.util.Date explícitas, o configurar toda la aplicación Java en el huso horario UTC (porque esto podría llevar a otros problemas inesperados, si la JVM se comparte en muchas aplicaciones).

Entonces, hay un proyecto de código abierto DbAssist , que le permite arreglar fácilmente la lectura / escritura como fecha UTC de la base de datos. Dado que está utilizando Anotaciones JPA para mapear los campos de la entidad, todo lo que tiene que hacer es incluir la siguiente dependencia en su archivo Maven pom :

<dependency> <groupId>com.montrosesoftware</groupId> <artifactId>DbAssist-5.2.2</artifactId> <version>1.0-RELEASE</version> </dependency>

A continuación, aplique la solución (para el ejemplo de Hibernate + Spring Boot) agregando la anotación @EnableAutoConfiguration antes de la clase de aplicación Spring. Para otras configuraciones, instrucciones de instalación y más ejemplos de uso, solo consulte el github del proyecto.

Lo bueno es que no tienes que modificar las entidades en absoluto; puedes dejar sus campos java.util.Date tal como están.

5.2.2 tiene que corresponderse con la versión de Hibernate que está utilizando. No estoy seguro de qué versión está utilizando en su proyecto, pero la lista completa de las correcciones proporcionadas está disponible en la página wiki del proyecto github . La razón por la que la solución es diferente para varias versiones de Hibernate es porque los creadores de Hibernate cambiaron la API un par de veces entre las versiones.

Internamente, la solución usa sugerencias de divestoclimb, Shane y algunas otras fuentes para crear un UtcDateType personalizado. Luego, asigna el java.util.Date estándar con el UtcDateType personalizado que maneja todo el manejo de zona horaria necesario. El mapeo de los tipos se logra usando la anotación @Typedef en el archivo @Typedef proporcionado.

@TypeDef(name = "UtcDateType", defaultForType = Date.class, typeClass = UtcDateType.class), package com.montrosesoftware.dbassist.types;

Puede encontrar un artículo here que explica por qué ocurre un cambio de tiempo y cuáles son los enfoques para resolverlo.


Hay varias zonas horarias en operación aquí:

  1. Clases de fecha de Java (util y sql), que tienen zonas horarias implícitas de UTC
  2. La zona horaria en la que se ejecuta su JVM, y
  3. la zona horaria predeterminada de su servidor de base de datos.

Todos estos pueden ser diferentes. Hibernate / JPA tiene una deficiencia de diseño grave en el sentido de que un usuario no puede garantizar fácilmente que la información de la zona horaria se conserve en el servidor de la base de datos (lo que permite la reconstrucción de las fechas y horas correctas en la JVM).

Sin la capacidad de (fácilmente) almacenar la zona horaria utilizando JPA / Hibernate, la información se pierde y, una vez que se pierde la información, resulta costoso construirla (si es posible).

Yo diría que es mejor almacenar siempre la información de zona horaria (debería ser la predeterminada) y los usuarios deberían tener la capacidad opcional de optimizar la zona horaria (aunque realmente solo afecta la visualización, todavía hay una zona horaria implícita en cualquier fecha).

Lo sentimos, esta publicación no ofrece una solución alternativa (se ha respondido en otro lugar), pero es una racionalización de por qué siempre es importante almacenar información de la zona horaria. Desafortunadamente, parece que muchos informáticos y programadores argumentan en contra de la necesidad de zonas horarias simplemente porque no aprecian la perspectiva de "pérdida de información" y cómo eso dificulta la internacionalización, lo cual es muy importante hoy en día con sitios web accesibles por Internet. clientes y personas en su organización mientras se mueven por el mundo.


Hibernate ignora las cosas de la zona horaria en fechas (porque no las hay), pero en realidad es la capa JDBC la que está causando problemas. ResultSet.getTimestamp y PreparedStatement.setTimestamp dicen en sus documentos que transforman las fechas a / de la zona horaria JVM actual de forma predeterminada al leer y escribir desde / a la base de datos.

Se me ocurrió una solución a esto en Hibernate 3.5 subclasificando org.hibernate.type.TimestampType que obliga a estos métodos JDBC a usar UTC en lugar de la zona horaria local:

public class UtcTimestampType extends TimestampType { private static final long serialVersionUID = 8088663383676984635L; private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); @Override public Object get(ResultSet rs, String name) throws SQLException { return rs.getTimestamp(name, Calendar.getInstance(UTC)); } @Override public void set(PreparedStatement st, Object value, int index) throws SQLException { Timestamp ts; if(value instanceof Timestamp) { ts = (Timestamp) value; } else { ts = new Timestamp(((java.util.Date) value).getTime()); } st.setTimestamp(index, ts, Calendar.getInstance(UTC)); } }

Lo mismo se debe hacer para corregir TimeType y DateType si usa esos tipos. La desventaja es que tendrá que especificar manualmente que estos tipos se utilizarán en lugar de los valores predeterminados en cada campo de Fecha en sus POJO (y también rompe la compatibilidad pura con JPA), a menos que alguien sepa de un método de anulación más general.

ACTUALIZACIÓN: Hibernate 3.6 ha cambiado los tipos de API. En 3.6, escribí una clase UtcTimestampTypeDescriptor para implementar esto.

public class UtcTimestampTypeDescriptor extends TimestampTypeDescriptor { public static final UtcTimestampTypeDescriptor INSTANCE = new UtcTimestampTypeDescriptor(); private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); public <X> ValueBinder<X> getBinder(final JavaTypeDescriptor<X> javaTypeDescriptor) { return new BasicBinder<X>( javaTypeDescriptor, this ) { @Override protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException { st.setTimestamp( index, javaTypeDescriptor.unwrap( value, Timestamp.class, options ), Calendar.getInstance(UTC) ); } }; } public <X> ValueExtractor<X> getExtractor(final JavaTypeDescriptor<X> javaTypeDescriptor) { return new BasicExtractor<X>( javaTypeDescriptor, this ) { @Override protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return javaTypeDescriptor.wrap( rs.getTimestamp( name, Calendar.getInstance(UTC) ), options ); } }; } }

Ahora, cuando se inicia la aplicación, si configura TimestampTypeDescriptor.INSTANCE en una instancia de UtcTimestampTypeDescriptor, todas las marcas de tiempo se almacenarán y tratarán como UTC sin tener que cambiar las anotaciones en los POJO. [No he probado esto todavía]


Hibernate no permite especificar zonas horarias por anotación o cualquier otro medio. Si utiliza el calendario en lugar de la fecha, puede implementar una solución mediante la propiedad HIbernate AccessType e implementar el mapeo usted mismo. La solución más avanzada es implementar un UserType personalizado para mapear su Fecha o Calendario. Ambas soluciones se explican en esta publicación de blog: http://dev-metal.blogspot.com/2010/11/mapping-dates-and-time-zones-with.html


La fecha no está en ningún huso horario (es una oficina de milisegundos desde un momento definido en el tiempo igual para todos), pero los DB subyacentes (R) generalmente almacenan marcas de tiempo en formato político (año, mes, día, hora, minuto, segundo, ...). ..) que es sensible a la zona horaria.

Para ser serio, Hibernate DEBE permitir que se le diga dentro de alguna forma de mapeo que la fecha de la base de datos se encuentra en tal y ese tipo de zona horaria, de modo que cuando la carga o la almacena no la asuma ...


Por favor, eche un vistazo a mi proyecto en Sourceforge que tiene tipos de usuario para tipos de fecha y hora de SQL estándar, así como JSR 310 y Joda Time. Todos los tipos intentan abordar el problema de la compensación. Ver http://sourceforge.net/projects/usertype/

EDITAR: En respuesta a la pregunta de Derek Mahar adjunta a este comentario:

"Chris, ¿tus tipos de usuarios funcionan con Hibernate 3 o superior? - Derek Mahar Nov 7 ''10 a las 12:30"

Sí, estos tipos son compatibles con las versiones de Hibernate 3.x, incluido Hibernate 3.6.


Según mi leal saber y entender, debes poner toda tu aplicación Java en la zona horaria UTC (para que Hibernate almacene las fechas en UTC), y necesitarás convertirla a la zona horaria deseada cuando muestres cosas (al menos lo hacemos) de esta manera).

Al inicio, hacemos:

TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC"));

Y configure la zona horaria deseada para el DateFormat:

fmt.setTimeZone(TimeZone.getTimeZone("Europe/Budapest"))


Usted pensaría que este problema común sería resuelto por Hibernate. ¡Pero no lo es! Hay algunos "hacks" para hacerlo bien.

El que yo uso es almacenar la Fecha como un Largo en la base de datos. Así que siempre estoy trabajando con milisegundos después del 1/1/70. Luego tengo getters y setters en mi clase que devuelven / aceptan solo fechas. Entonces, la API sigue siendo la misma. El inconveniente es que tengo largos en la base de datos. SO con SQL, puedo hacer prácticamente solo comparaciones <,>, =, no con operadores de fecha extravagantes.

Otro enfoque es utilizar un tipo de mapeo personalizado como se describe aquí: http://www.hibernate.org/100.html

Creo que la forma correcta de lidiar con esto es utilizar un calendario en lugar de una fecha. Con el calendario, puede configurar la zona horaria antes de continuar.

NOTA: tonto no me deja comentar, así que aquí hay una respuesta a david a.

Si creas este objeto en Chicago:

new Date(0);

Hibernate lo persiste como "31/12/1969 18:00:00". Las fechas deben estar desprovistas de zona horaria, por lo que no estoy seguro de por qué se realizará el ajuste.