2015-09-13 4 views
20

Kann mir irgendein Java 8 + JDBC-Experte sagen, ob etwas in der folgenden Argumentation falsch ist? Und wenn dies in den Geheimnissen der Götter geschieht, warum wurde das nicht getan?Verpasste Gelegenheit, JDBC-Datumsverarbeitung in Java 8 zu beheben?

Ein java.sql.Date ist derzeit der Typ, der von JDBC verwendet wird, um dem DATE-SQL-Typ zuzuordnen, der ein Datum ohne Zeit und ohne Zeitzone darstellt. Aber diese Klasse ist schrecklich entworfen, da es in der Tat eine Unterklasse von java.util.Date ist, die einen genauen Zeitpunkt in der Millisekunde speichert.

Um das Datum 2015.09.13 in der Datenbank zu repräsentieren, sind wir somit gezwungen, eine Zeitzone zu wählen, analysieren die Zeichenfolge „2015-09-13T00: 00: 00.000“ in dieser Zeitzone als java.util.Date Um einen Millisekunden-Wert zu erhalten, konstruiere dann einen java.sql.Date von diesem Millisekunden-Wert und rufe schließlich setDate() auf der vorbereiteten Anweisung an und übergibt einen Kalender mit der gewählten Zeitzone, damit der JDBC-Treiber das Datum 2015-09-13 korrekt neu berechnen kann von diesem Millisekundenwert. Dieser Prozess wird durch die Verwendung der Standardzeitzone überall und das Übergeben eines Kalenders vereinfacht.

Java 8 führt eine LocalDate-Klasse ein, die viel besser zum DATE-Datenbanktyp passt, da es sich nicht um einen genauen Zeitpunkt handelt und somit nicht von der Zeitzone abhängig ist. Und Java 8 führt auch Standardmethoden ein, die es ermöglichen, abwärtskompatible Änderungen an den Interfaces PreparedStatement und ResultSet vorzunehmen.

Also, haben wir nicht eine riesige Gelegenheit verpasst, die Unordnung in JDBC zu bereinigen, während wir immer noch Abwärtskompatibilität beibehalten?

default public void setLocalDate(int parameterIndex, LocalDate localDate) { 
    if (localDate == null) { 
     setDate(parameterIndex, null); 
    } 
    else { 
     ZoneId utc = ZoneId.of("UTC"); 
     java.util.Date utilDate = java.util.Date.from(localDate.atStartOfDay(utc).toInstant()); 
     Date sqlDate = new Date(utilDate.getTime()); 
     setDate(parameterIndex, sqlDate, Calendar.getInstance(TimeZone.getTimeZone(utc))); 
    } 
} 

default LocalDate getLocalDate(int parameterIndex) { 
    ZoneId utc = ZoneId.of("UTC"); 
    Date sqlDate = getDate(parameterIndex, Calendar.getInstance(TimeZone.getTimeZone(utc))); 
    if (sqlDate == null) { 
     return null; 
    } 

    java.util.Date utilDate = new java.util.Date(sqlDate.getTime()); 
    return utilDate.toInstant().atZone(utc).toLocalDate(); 
} 

Natürlich ist die gleiche Argumentation gilt für die Unterstützung von Sofort für den Zeitstempel-Typen, und die Unterstützung von Localtime für den TIME-Typen: Java 8 einfach haben könnte, um diese Standardmethoden PreparedStatement und ResultSet hinzugefügt.

+2

Statt das zu tun, sagen Sie sich einfach die 'setObject' Methode zu verwenden. Wenn das Objekt ein 'LocalDate 'ist, sollten aktuelle JDBC-Treiber wissen, was damit zu tun ist. – RealSkeptic

+0

Ich habe das gesehen. Aber es ist schade, weil wir auf aktuelle Treiber warten müssen, und Bibliotheken können nie sicher sein, dass der JDBC-Treiber tatsächlich LocalDate unterstützt, also implementieren sie diese Funktionalität neu, anstatt sich auf eine Standardimplementierung zu verlassen. Und wir müssen getDate(). ToLocalDate() auf einem ResultSet ausführen, um ein LocalDate zu erhalten. –

+0

Oder verwenden Sie ein 'getObject (int, Class)' und verlassen Sie sich auf den JDBC-Treiber, um damit umgehen zu können. Wie auch immer, das ist ihre Politik für jetzt. Die Frage ist, welche Art von Antworten erwarten Sie von SO zu diesem Thema? – RealSkeptic

Antwort

21

Um das Datum 2015.09.13 in der Datenbank zu repräsentieren, sind wir somit gezwungen, eine Zeitzone zu wählen, analysieren die Zeichenfolge „2015-09-13T00: 00: 00.000“ in dieser Zeitzone als java.util .Date, um einen Millisekundenwert zu erhalten, dann ein java.sql.Date aus diesem Millisekundenwert aufzubauen und schließlich setDate() auf der vorbereiteten Anweisung aufzurufen und einen Kalender mit der gewählten Zeitzone zu übergeben, damit der JDBC-Treiber korrekt arbeiten kann neu berechnet das Datum 2015.09.13 von diesem Wert in Millisekunden

Warum? Rufen Sie einfach

Date.valueOf("2015-09-13"); // From String 
Date.valueOf(localDate); // From java.time.LocalDate 

Das Verhalten wird die richtige in allen JDBC-Treiber sein: ohne Zeitzone um das lokale Datum. Die inverse Operation ist:

date.toString(); // To String 
date.toLocalDate(); // To java.time.LocalDate 

Sie nie auf java.sql.Date verlassen sollten 's Abhängigkeit von java.util.Date, und die Tatsache, dass es so um die Semantik eines java.time.Instant über Date(long) oder Date.getTime()

erbt also haven' t haben wir eine große Chance verpasst, die Unordnung in JDBC zu beseitigen, während wir weiterhin Abwärtskompatibilität beibehalten? [...]

Es hängt davon ab. Die JDBC 4.2 spec gibt an, dass Sie in der Lage sind, einen LocalDate-Typ über setObject(int, localDate) zu binden und einen LocalDate-Typ über getObject(int, LocalDate.class) abzurufen, wenn der Treiber dafür bereit ist.Nicht so elegant wie formelle default Methoden, wie Sie vorgeschlagen, natürlich.

+2

Ich habe die Methode valueOf (LocalDate) verpasst (ich suchte nach einer from() -Methode wie 'java.util.Date.from (Instant)') und habe die Methode valueOf (String) vergessen. Es ist immer noch Chaos, da wir wissen müssen, welche Zeitzone beim Erstellen des Datums verwendet wurde, um die richtige Methode setDate() aufzurufen. Ich bin mir nicht sicher, wie ich es vermeiden kann, mich auf die Tatsache zu verlassen, dass es Millisekunden verwendet, da der einzige nicht veraltete Konstruktor Millisekunden als Argument benötigt. –

+1

In Bezug auf den letzten Teil finde ich es hässlich: Wir müssen auf Java8-kompatible Treiber warten, und es ist bei weitem nicht offensichtlich, dass setObject()/getObject() anstelle der üblichen setXxx()/getXxx() -Methoden verwendet werden muss für alle anderen Arten. LocalDate wird im Javadoc nicht einmal erwähnt. –

+5

Es ist ein hässliches Chaos, ja. Mit 'java.sql.Date' wird es etwas einfacher, wenn Sie es nur als Container der String-Version des Datums betrachten. Versuchen Sie jedoch, keine serialisierte Instanz von 'java.sql.Date' an einen Client in einer anderen Zeitzone zu senden. –

5

Kurze Antwort: Nein, die Verarbeitung von Datumszeit ist in Java 8/JDBC 4.2 behoben, da sie Java 8 Date and Time types unterstützt. Dies kann nicht mit Standardmethoden emuliert werden.

Java 8 einfach haben könnten, um diese Standardmethoden PreparedStatement und ResultSet hinzugefügt:

Dies aus mehreren Gründen nicht möglich ist:

  • java.time.OffsetDateTime hat nicht gleichwertig in java.sql
  • java.time.LocalDateTime bricht bei DST-Übergängen bei der Konvertierung durch java.sql.Timestamp ab, weil letzteres von der JVM t abhängt ime Zone, siehe Code unten
  • java.sql.Time hat eine Auflösung Millisekunden aber java.time.LocalTime hat Auflösung Nanosekunden

Deshalb müssen Sie die richtige Treiber-Unterstützung, Standardmethoden müßten durch java.sql Typen konvertieren, den Datenverlust führt.

Wenn Sie diesen Code in einer JVM-Zeitzone mit Sommerzeit ausführen, wird er unterbrochen. Es sucht den nächsten Übergang, in dem die Uhren "vorgerückt" sind und wählt eine LocalDateTime, die mitten in dem Übergang ist. Dies ist vollkommen korrekt, da Java LocalDateTime oder SQL TIMESTAMP keine Zeitzone und daher keine Zeitzonenregeln und daher keine Sommerzeit haben. java.sql.Timestamp ist dagegen an die JVM-Zeitzone gebunden und unterliegt daher der Sommerzeit.

ZoneId systemTimezone = ZoneId.systemDefault(); 
Instant now = Instant.now(); 

ZoneRules rules = systemTimezone.getRules(); 
ZoneOffsetTransition transition = rules.nextTransition(now); 
assertNotNull(transition); 
if (!transition.getDateTimeBefore().isBefore(transition.getDateTimeAfter())) { 
    transition = rules.nextTransition(transition.getInstant().plusSeconds(1L)); 
    assertNotNull(transition); 
} 

Duration gap = Duration.between(transition.getDateTimeBefore(), transition.getDateTimeAfter()); 
LocalDateTime betweenTransitions = transition.getDateTimeBefore().plus(gap.dividedBy(2L)); 

Timestamp timestamp = java.sql.Timestamp.valueOf(betweenTransitions); 
assertEquals(betweenTransitions, timestamp.toLocalDateTime());