Come memorizzare data / ora e data e ora nel fuso orario UTC con JPA e Hibernate

Come posso configurare JPA / Hibernate per memorizzare una data / ora nel database come fuso orario UTC (GMT)? Considera questa quadro JPA annotata:

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

Se la data è 2008-Feb-03 9:30 am Pacific Standard Time (PST), quindi voglio l’ora UTC di 2008-Feb-03 5:30 pm memorizzati nel database. Allo stesso modo, quando la data viene recuperata dal database, la voglio interpretata come UTC. Quindi in questo caso 530pm è 5:30 pm UTC. Quando viene visualizzato, verrà formattato come 9:30 PST.

Con Hibernate 5.2, ora puoi forzare il fuso orario UTC usando la seguente proprietà di configurazione:

  

Per maggiori dettagli, consulta questo articolo .

Per quanto ne so, devi inserire l’intera app Java nel fuso orario UTC (in modo che Hibernate memorizzi le date in UTC) e dovrai convertire in qualsiasi fuso orario desiderato quando visualizzi materiale (almeno lo facciamo per di qua).

All’avvio, facciamo:

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

E imposta il fuso orario desiderato su DateFormat:

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

Hibernate ignora il fuso orario in Date (perché non ce n’è), ma in realtà è il livello JDBC a causare problemi. ResultSet.getTimestamp e PreparedStatement.setTimestamp dicono entrambi nei propri documenti che trasformano le date in / dal fuso orario JVM corrente per impostazione predefinita durante la lettura e la scrittura da / verso il database.

Ho trovato una soluzione a questo in Hibernate 3.5 sottoclass org.hibernate.type.TimestampType che forza questi metodi JDBC a utilizzare UTC invece del fuso orario locale:

 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)); } } 

La stessa cosa dovrebbe essere fatta per correggere TimeType e DateType se si utilizzano quei tipi. Lo svantaggio è che devi specificare manualmente che questi tipi devono essere usati al posto dei valori predefiniti su ogni campo Data nei tuoi POJO (e interrompe anche la pura compatibilità con JPA), a meno che qualcuno non conosca un metodo di override più generale.

AGGIORNAMENTO: Hibernate 3.6 ha cambiato i tipi API. In 3.6, ho scritto una class UtcTimestampTypeDescriptor per implementare questo.

 public class UtcTimestampTypeDescriptor extends TimestampTypeDescriptor { public static final UtcTimestampTypeDescriptor INSTANCE = new UtcTimestampTypeDescriptor(); private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); public  ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( 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  ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { @Override protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return javaTypeDescriptor.wrap( rs.getTimestamp( name, Calendar.getInstance(UTC) ), options ); } }; } } 

Ora all’avvio dell’app, se si imposta TimestampTypeDescriptor.INSTANCE su un’istanza di UtcTimestampTypeDescriptor, tutti i timestamp verranno archiviati e trattati come in UTC senza dover modificare le annotazioni sui POJO. [Non l’ho ancora testato]

Aggiungendo una risposta completamente basata su e indebitata per disinvestire con un suggerimento di Shaun Stone. Volevo semplicemente spiegarlo in dettaglio poiché si tratta di un problema comune e la soluzione è un po ‘confusa.

Questo sta usando Hibernate 4.1.4.Final, anche se ho il sospetto che qualsiasi cosa dopo 3.6 funzionerà.

Innanzitutto, crea UtcTimestampTypeDescriptor di divestoclimb

 public class UtcTimestampTypeDescriptor extends TimestampTypeDescriptor { public static final UtcTimestampTypeDescriptor INSTANCE = new UtcTimestampTypeDescriptor(); private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); public  ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( 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  ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { @Override protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return javaTypeDescriptor.wrap( rs.getTimestamp( name, Calendar.getInstance(UTC) ), options ); } }; } } 

Quindi creare UtcTimestampType, che utilizza UtcTimestampTypeDescriptor anziché TimestampTypeDescriptor come SqlTypeDescriptor nella chiamata super-costruttore, ma delegherà tutto a TimestampType:

 public class UtcTimestampType extends AbstractSingleColumnStandardBasicType implements VersionType, LiteralType { 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 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); } } 

Infine, quando si inizializza la configurazione di Hibernate, registrare UtcTimestampType come sovrascrittura del tipo:

 configuration.registerTypeOverride(new UtcTimestampType()); 

Ora i timestamp non dovrebbero preoccuparsi del fuso orario della JVM sulla loro strada da e verso il database. HTH.

Penseresti che questo problema comune verrebbe risolto da Hibernate. Ma non lo è! Ci sono alcuni “hack” per farlo bene.

Quello che uso è quello di memorizzare la data come una lunga nel database. Quindi lavoro sempre con millisecondi dopo il 1/1/70. Poi ho getter e setter sulla mia class che restituiscono / accettano solo date. Quindi l’API rimane la stessa. Il lato negativo è che ho molto tempo nel database. SO con SQL I può fare quasi solo <,>, = confronti – non gli operatori di data fantasia.

Un altro approccio consiste nell’utilizzare un tipo di mapping personalizzato come descritto qui: http://www.hibernate.org/100.html

Penso che il modo corretto per affrontare questo è di utilizzare un calendario invece di una data però. Con il Calendario puoi impostare il TimeZone prima di persistere.

NOTA: Silly StackOverflow non mi consente di commentare, quindi ecco una risposta a David A.

Se crei questo object a Chicago:

 new Date(0); 

Hibernate lo persiste come “31/12/1969 18:00:00”. Le date dovrebbero essere prive di fuso orario, quindi non sono sicuro del motivo per cui sarebbe stato effettuato l’aggiustamento.

Qui ci sono diversi fusi orari:

  1. Classi Date di Java (util e sql), che hanno timez impliciti di UTC
  2. Il fuso orario in cui la JVM è in esecuzione, e
  3. il fuso orario predefinito del server del database.

Tutti questi possono essere diversi. Hibernate / JPA presenta una grave carenza di progettazione in quanto un utente non può facilmente assicurare che le informazioni sul fuso orario siano conservate nel server di database (che consente la ricostruzione di orari e date corrette nella JVM).

Senza la possibilità di memorizzare (facilmente) il fuso orario utilizzando JPA / Hibernate, le informazioni vengono perse e una volta perse l’informazione diventa costoso costruirla (se ansible).

Direi che è meglio memorizzare sempre le informazioni sul fuso orario (dovrebbe essere l’impostazione predefinita) e gli utenti dovrebbero avere la possibilità opzionale di ottimizzare il fuso orario (anche se influenza davvero solo la visualizzazione, c’è ancora un fuso orario implicito in qualsiasi data).

Spiacente, questo post non fornisce un work-around (che è stato risposto altrove) ma è una razionalizzazione del motivo per cui è sempre importante memorizzare le informazioni sul fuso orario. Sfortunatamente sembra che molti scienziati informatici e professionisti della programmazione sostengano la necessità di fusi orari semplicemente perché non apprezzano la prospettiva della “perdita di informazioni” e come ciò rende molto difficili le cose come l’internazionalizzazione – che è molto importante in questi giorni con i siti web accessibili da clienti e persone nella tua organizzazione mentre si muovono in tutto il mondo.

Si prega di dare un’occhiata al mio progetto su Sourceforge che ha tipi di utente per i tipi di data e ora SQL standard, nonché JSR 310 e Joda Time. Tutti i tipi cercano di affrontare il problema della compensazione. Vedi http://sourceforge.net/projects/usertype/

EDIT: in risposta alla domanda di Derek Mahar allegata a questo commento:

“Chris, i tuoi tipi di utenti funzionano con Hibernate 3 o versioni successive? – Derek Mahar 7 novembre 2010 alle 12:30”

Sì, questi tipi supportano le versioni di Hibernate 3.x incluso Hibernate 3.6.

La data non è in nessun fuso orario (è un ufficio millisecondo da un momento definito nel tempo uguale per tutti), ma i DB sottostanti (R) generalmente memorizzano i timestamp in formato politico (anno, mese, giorno, ora, minuto, secondo,. ..) che è sensibile al fuso orario.

Per essere seri, Hibernate DEVE essere permesso di essere informato in una qualche forma di mapping che la data del DB è in un fuso orario così e così in modo che quando lo carica o lo memorizza non assume il proprio …

Hibernate non consente di specificare i fusi orari mediante annotazioni o altri mezzi. Se si utilizza Calendar invece di data, è ansible implementare una soluzione alternativa utilizzando la proprietà HIbernate AccessType e implementando la mapping autonomamente. La soluzione più avanzata consiste nell’implementare un tipo utente personalizzato per mappare la data o il calendario. Entrambe le soluzioni sono spiegate in questo post del blog: http://dev-metal.blogspot.com/2010/11/mapping-dates-and-time-zones-with.html

Ho riscontrato lo stesso problema quando volevo memorizzare le date nel DB come UTC ed evitare di utilizzare le conversioni varchar ed esplicite String <-> java.util.Date , o di impostare la mia intera app Java nel fuso orario UTC (perché ciò potrebbe portare a un altro problema inaspettato, se la JVM è condivisa tra molte applicazioni).

Quindi, esiste un progetto open source DbAssist , che consente di fissare facilmente la lettura / scrittura come data UTC dal database. Dato che stai usando le annotazioni JPA per mappare i campi nell’entity framework, tutto ciò che devi fare è includere la seguente dipendenza dal tuo file Maven pom :

  com.montrosesoftware DbAssist-5.2.2 1.0-RELEASE  

Quindi applichi la correzione (per esempio Hibernate + Spring Boot) aggiungendo @EnableAutoConfiguration annotazione @EnableAutoConfiguration prima della class dell’applicazione Spring. Per altre istruzioni di installazione e altri esempi di utilizzo, fai riferimento al github del progetto.

La cosa buona è che non devi affatto modificare le quadro; puoi lasciare i loro campi java.util.Date come sono.

5.2.2 deve corrispondere alla versione di Hibernate che si sta utilizzando. Non sono sicuro, quale versione stai usando nel tuo progetto, ma l’elenco completo delle correzioni fornite è disponibile sulla pagina wiki del github del progetto. Il motivo per cui la correzione è diversa per le varie versioni di Hibernate è perché i creatori di Hibernate hanno cambiato l’API un paio di volte tra le versioni.

Internamente, la correzione utilizza i suggerimenti di divestoclimb, Shane e alcune altre fonti per creare un UtcDateType personalizzato. Quindi esegue il mapping dello standard java.util.Date con l’ UtcDateType personalizzato che gestisce tutta la necessaria gestione del fuso orario. La mapping dei tipi viene ottenuta utilizzando @Typedef annotazione @Typedef nel file @Typedef fornito.

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

Qui puoi trovare un articolo che spiega perché si verifica un tale cambiamento di orario e quali sono gli approcci per risolverlo.