2014-03-05 3 views
7

ich Hibernate bin mit (4.2) als meine Persistenz-Provider, und ich habe eine JPA-Entität, die ein Datumsfeld enthält:Unerwünschte automatische Zeitzonenkonvertierung unter Ruhe/JPA und JDK Datum

@Entity 
@Table(name = "MY_TABLE") 
public class MyTable implements Serializable { 
    . . . 
    @Temporal(TemporalType.TIMESTAMP) 
    @Column(name = "START_DATE") 
    private Date startDate; 
    public Date getStartDate() { 
    return startDate; 
    } 
    public void setStartDate(Date startDate) { 
    this.startDate = startDate; 
    } 
    . . . 
} 

Die Spalte mit den entsprechenden START_DATE ist definiert als START_DATE TIMESTAMP (keine Zeitzone).

Ich bin mit Joda-Time (2.3) intern zu meiner Anmeldung des Tag zu beschäftigen (immer in UTC) und vor nur um die Entity persistierenden, verwende ich die toDate() Methode der DateTime Klasse Joda einen JDK zu bekommen Date Objekt, um die Zuordnung zu gehorchen:

public void myMethod(DateTime startDateUTC) { 
    . . . 
    MyTable table = /* obtain somehow */ 
    table.setStartDate(startDateUTC.toDate()); 
    . . . 
} 

Wenn ich mich in der DB auf dem Wert, der gespeichert wird, merke ich, dass irgendwo (JDK Hibernate?) die Standard-Zeitzone des JVM den Datumswert konvertiert unter Verwendung von wo der Code läuft. In meinem Fall ist das "Amerika/Chicago".

Das Problem manifestiert sich wirklich in der Nähe von Sommerzeit (DST). Zum Beispiel intern, wenn die Zeit ist

2014-03-09T02:55:00Z 

es gespeichert wird als

09-Mar-14 03:55:00 

Was möchte ich, ist es als

09-Mar-14 02:55:00 

jedoch in CDT gespeichert werden, 02.55 Uhr am 9. März existiert nicht ("Spring forward"). Irgendwas (JDK? Hibernate?) Rollt das Datum weiter.

Ich möchte für den Augenblick, der in der DB gespeichert wird, in UTC sein. Schließlich habe ich es intern mit meiner Anwendung zu tun, aber sobald ich es aushändige, wird es in meine Standardzeitzone umgewandelt.

Hinweis: Ich kann die Standard-Zeitzone einstellen

TimeZone.setDefault(TimeZone.getTimeZone("UTC")) 

weil die JVM verwenden, auf dem ich laufen über mehrere Anwendungen gemeinsam genutzt wird.

Wie speichere ich das Datum in UTC, ohne die JVM-Standardzeitzone auf UTC zu setzen?

+0

Meine Schätzung ist, dass wenn Sie das Datum in der Datenbank betrachten, das Tool, das Sie verwenden, die binäre numerische Darstellung des Datums, das die Datenbank intern verwendet, in etwas, das Sie verstehen können, umwandelt: ein Datum und eine Uhrzeit formatiert mit Ihrer Zeitzone. Genau wie wenn Sie new Date() machen. ToString(): Das timezone-agnostic Date-Objekt wird mit der Standardzeitzone zu einer Zeichenkette formatiert. –

Antwort

6

Ich lief selbst in diese. Was ich gesehen habe ist, dass, obwohl du UTC als Zeitzone in deinem Date angegeben hast (und dies sehen kannst, indem du es ausdruckst und das 'Z' am Ende siehst), aus irgendeinem Grund, will die JVM übernehmen und Konvertieren Sie das Datum für Sie mithilfe der Standardzeitzone der JVM.

Wie auch immer, was Sie brauchen, ist eine benutzerdefinierte Zuordnung, um dies zu umgehen.Versuchen Sie Jadira mit:

@Entity 
@Table(name = "MY_TABLE") 
public class MyTable implements Serializable { 
    . . . 
    @Column(name = "START_DATE") 
    @Type(type="org.jadira.usertype.dateandtime.legacyjdk.PersistentDate") 
    private Date startDate; 
    public Date getStartDate() { 
    return startDate; 
    } 
    public void setStartDate(Date startDate) { 
    this.startDate = startDate; 
    } 
    . . . 
} 

standardmäßig Jadira der PersistentDate Klasse verwendet UTC als Zeitzone, wenn das Datum auf die Millisekunde Wert umwandelt, die in der DB gespeichert wird. Sie können andere Zeitzonen angeben, aber es klingt wie UTC, was Sie speichern möchten. Wie der Kommentar zu Ihrem Post suggeriert, macht manchmal das Tool, das Sie verwenden, um die DB abzufragen, die gedankenlos dumme automatische-was-ist-mein-JDK-Standard-TZ-basierte Konvertierung für Sie, was Sie glauben lässt, dass der Wert immer noch ist falsch.

Sie können auch versuchen, den Rohwert (als INTEGER) zu speichern, nur um sich davon zu überzeugen, dass der richtige Millisekundenwert gespeichert wird.

HTH,

Mose

+0

Das hat es geschafft! Vielen Dank! –

+0

es funktioniert wie ein Charme ... Danke dafür! – Daniele

+0

Danke für die Antwort. Irgendeine Lösung, um das gleiche mit Eclipselink zu machen? – clapsus

-2

Haben Sie versucht, getDateTime (DateTimeZone x) vom DateTime-Objekt zu verwenden?

Etwas wie folgt aus:

public void myMethod(DateTime startDateUTC) { 
. . . 
    MyTable table = /* obtain somehow */ 
    table.setStartDate(startDateUTC.toDateTime(DateTimeZone.UTC)); 
    . . . 
} 

Hoffe, es hilft Ihnen!

+0

Das hereinkommende DateTime-Objekt ist bereits in UTC, wie ich versucht habe, durch den Parameternamen anzuzeigen. –

+0

Sorry, habe das nicht bemerkt.Vielleicht mit LocalDateTime, wie Sie in joda Zeit Dokumentation sehen können, berücksichtigt es nicht die Zeitzone. [LocalDateTime] (http://joda-time.sourceforge.net/apidocs/org/joda/time/LocalDateTime.html) –

+0

Ich möchte die Zeitzone, ich möchte nur, dass es UTC ist. Wenn LocalDateTime verwendet wird, wird der Zeitstempel mit dem Standardwert der JVM berücksichtigt, der die Wurzel des Problems darstellt. –

4

Es gibt einen Artikel über diese unerwartete Zeitzone Verschiebung Frage, die Sie here überprüfen können. Es liefert die Erklärung für die Wurzel des Problems und zeigt, wie damit umzugehen ist. Die Hauptvermutung ist natürlich, dass wir Daten als UTC in der Datenbank speichern wollen.

Wenn Sie beispielsweise ein Datum aus der Datenbank lesen (sagen wir: 9:54 UTC), überspringt JDBC alle Informationen über die Zeitzone. Was JVM über JDBC erhält, ist also ein Datum, das so interpretiert wird, als wäre es in der lokalen Zeitzone (für meinen Fall 9:54 UTC + 2). Wenn die lokale Zeitzone von UTC abweicht (und dies normalerweise tut), endet die falsche Zeitverschiebung.

Die ähnliche Situation tritt auf, wenn in den DB geschrieben wird.

Es gibt ein kleines Open-Source-Projekt DbAssist, das Fixes für verschiedene Versionen von Hibernate bereitstellt. Wenn Sie also Hibernate 4.2.21 verwenden, fügen Sie Ihrer POM-Datei einfach die folgende Maven-Abhängigkeit hinzu, und Ihr Problem ist gelöst (die ausführlichen Installationsanweisungen für das Setup mit z. B. Spring Boot finden Sie in der Bibliothek github).

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

Nachdem dieser Fix angewendet wird Ihre java.util.Date Felder in den Entitätsklassen gelesen werden und blieb wie erwartet: als ob sie als UTC in der Datenbank gespeichert wurden. Wenn Sie JPA-Anmerkungen verwenden, müssen Sie die Zuordnung in den Entitäten nicht ändern (wie in einer der vorherigen Antworten). es wird automatisch gemacht.

Intern verwendet das Update einen benutzerdefinierten UTC-Datumstyp, der den Datumstyp von Hibernate überschreibt, um zu erzwingen, dass alle Daten im DB als UTC behandelt werden. Um dann das Mapping von java.util.Date bis UtcDateType zu übernehmen, verwendet es @Typedef Annotation. Siehe unten:

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

Wenn Ihr Projekt ist abhängig von Hibernate HBM-Dateien oder anderen Versionen von Hibernate, geht auf GitHub Wiki des Projektes für detaillierte Anweisungen, wie Sie die richtige Lösung zu installieren.

Verwandte Themen