Vorwort
Die Time-API von Java 8 soll die bisherigen Implementierungen von Date und Calendar ablösen. Die Benefits der neuen API sind
- Thread Sicherheit (Thread safety)
- Unveränderlichkeit (Immutability)
- intuitive Schnittstellen
Die API besteht aus 5 packages in der Java-API
| package | Beschreibung |
|---|---|
| java.time |
wichtigster Teil der Time-API für Datum, Zeit, Zeitstempel und Zeiträume
|
| java.time.chrono |
Generischer Teil der API für Kalendarien, die von der Standard ISO abweichen
|
| java.time.format |
Klassen um Datum und Zeit in einen String umzuwandeln und umgekehrt
|
| java.time.temporal |
Zugriff auf Felder und Einheiten sowie Korrekturmöglichkeiten für Datum und Zeit
|
| java.time.zone |
Unterstützung für Zeitzonen und ihre Regeln
|
Die wichtigsten Klassen
Die wohl wichtigsten Klassen, die die Time-API zu bieten hat sind:
| Klasse | Bemerkung |
| Temporal | Interface für die Zeit- und Datumsklassen der Time-API |
| LocalDate | Beschreibt das lokale Datum ohne Berücksichtigung der Zeitzone |
| LocalTime | Beschreibt die lokale Zeit ohne Berücksichtigung der Zeitzone |
| LocalDateTime | Beschreibt die Kombination aus den lokalen Daten von Zeit und Datum ohne Berücksichtigung der Zeitzone |
| Duration | Beschreibt die Dauer eines Zeitraums und ist damit zeitzonenfrei |
| Period | Beschreibt einen Zeitspanne gem. ISO-8601 und ist, wie Duration, ebenso zeitzonenfrei |
| ZonedDateTime | Beschreibt die Kombination aus Zeit und Datum inklusive der Zeitzone |
Die weiteren Klassen des Packages java.time werden hier nicht vorgestellt, obwohl auch diese für bestimmte Aufgaben interessant sein können.
Datumsformatierung
Bisher kennen wir die abstrakte Klasse DateFormat mit ihrer Standardimplementierung SimpleDateFormat, um Date– oder Calendar Objekte in einen lesbaren String zu verwandeln. Dabei konnte der SimpleDateFormat vordefinierte oder selbst definierte Pattern nutzen, um ein Date oder Calendar Objekt in einen String oder umgekehrt zu verwandeln. Der Haken: der Entwickler musste selber aufpassen, dass er nicht versehentlich eine Instanz, die bereits genutzt wurde ändert, da diese, analog zur Date und Calendar Klasse, veränderbar ist. Dadurch ist die Standardimplementierung SimpleDateFormat auch nicht threadsafe. (Eigene Implementierungen von DateFormat könnten diese Sicherheit bieten)
Nun haben wir im Package java.time.format eine neue Klasse zur Formatierung von Datums- und Zeitwerten. Im Gegensatz zum SimpleDateFormat ist diese Klasse unveränderlich und thread safe – alle Methoden des DateTimeFormatter, die ein DateTimeFormatter liefern, geben immer eine neue Instanz eines DateTimeFormatter zurück. Auch der DateTimeFormatter kann mit vordefinierten oder selbst definierten Pattern erstellt werden, um die neuen Temporals in einen String umzuwandeln oder umgekehrt.
Der ebenfalls im Package java.time.format enthaltene DateTimeFormatterBuilder ist veränderlich und damit nicht thread safe. Das muss er aber auch nicht sein, da die DateTimeFormatter, die mit diesem Builder erstellt werden, wiederum unveränderlich und thread safe sind. Die Klasse DateTimeFormatter nutzt den Builder, um eine Instanz von sich selbst zu erstellen – auch dann, wenn wir einen DateTimeFormatter z.B. mit DateTimeFormatter.ISO_LOCAL_DATEerstellen.
Time-API und die Datenbank
Vor Java 8
Bis Java 8 kannte man die Datentypen java.util.Date, java.util.Calendar. Für das Speichern in einer Datenbank werden aber andere Datentypen benötigt – namentlich: java.sql.Datejava.sql.Date, java.sql.Time und java.sql.Timestamp. Hier gab es zwei Möglichkeiten: entweder nutzte man die SQL-Datentypen in den Entities und kümmerte sich manuell um die Konvertierung, oder man nutzte die Klassen aus java.util und annotierte diese entsprechend mit @Temporal(TemploralType)
Um ein Datum oder einen Zeitstempel in die Datenbank zu schreiben wurde eine Entity bisher so aufgebaut:
@Entity
@Table(name = "tablename")
public class SampleTable {
@Column(name="Liefertermin")
@Temporal(TemporalType.DATE)
private java.util.Date deliveryDate;
@Column(name="Bestellungseingang")
@Temporal(TemporalType.TIMESTAMP)
private java.util.Date incomingOrder;
@Column(name="Bearbeitungszeit")
@Temporal(TemporalType.TIME)
private java.util.Date timeOfRework;
}
An der Stelle von java.util.Date kann auch java.util. Calendar genutzt werden. Die Annotation @Temporal benötigt den angegebenen TemporalType, um aus dem java.util.Date oder java.util.Calendar ein java.sql.Date (TemporalType.DATE), javasql.Time (TIME) oder ein java.sql.Timestamp (TIMESTAMP) zu erstellen.
Seit Java 8
Mit den Klassen der neuen Java 8 Time-API funktionieren diese Annotationen aber nicht mehr. Es müssen also andere Mechanismen her, die uns die neuen Datentypen Datenbank kompatibel anpassen. In Java 8 reicht die einfache Annotation mit @Temporal nicht mehr aus. Der Aufwand der Datenkonvertierung wird nun dem Entwickler überlassen. Daraus ergeben sich folgende Änderung für unserer Sample Entity oben.
Die Struktur der Entity ändert sich nur geringfügig.
@Entity
@Table(name = "tablename")
public class SampleTable {
@Column(name="Liefertermin")
private java.time.LocalDate deliveryDate;
@Column(name="Bestellungseingang")
private java.time.LocalDateTime incomingOrder;
@Column(name="Bearbeitungszeit")
private java.time.Duration timeOfRework;
}
Hier wird schon an Hand der Datentypen klar, was gespeichert werden soll. Am Deutlichsten wird es noch bei der Bearbeitungszeit, bei der wir ja eigentlich keine irgendwie selbst berechnete Zeitspanne eintragen wollen, sondern schon alleine durch den Datentyp sichtbar machen, dass es sich hier um die Dauer des Vorgangs handelt.
Nun kann aber die JPA oder auch die diversen Datenbanken nichts mit der Java 8 Time-API anfangen. Daher müssen wir dafür sorgen, dass eine geeignete Konvertierung stattfindet. Zu beachten is, dass diese Konvertierung nur ab Java Persistence 2.1 möglich ist, da in dieser Version erst das Interface AttributeConverter zur Verfügung steht.
@Converter(autoApply = true)
public class LocalDateConverter implements AttributeConverter<java.time.LocalDate, java.sql.Date> {
@Override
public java.sql.Date convertToDatabaseColumn(final java.time.LocalDate attribute) {
return attribute == null ? null : java.sql.Date.valueOf(attribute);
}
@Override
public java.time.LocalDate convertToEntityAttribute(final java.sql.Date dbData) {
return dbData == null ? null : dbData.toLocalDate();
}
}
Analog zum LocalDateConverter können nun auch Konverter für LocalDateTime <-> Timestamp und Duration <-> Date etc erstellt werden.
Über das autoApply = true in der Klassen Annotation @Converter kann definiert werden, ob dieser Konverter automatisch auf ein Member in einer Entity angewandt werden soll oder nicht. Hier sollte man Vorsicht walten lassen, dass sich zwei Konverter nicht in die Quere kommen.
Alternativ zum autoApply = true können die Konverter auch an jede Spalten Deklaration geschrieben werden. Damit sähe die Entity so aus:
@Entity
@Table(name = "tablename")
public class SampleTable {
@Column(name="Liefertermin")
@Convert(converter = LocalDateConverter.class)
private java.time.LocalDate deliveryDate;
@Column(name="Bestellungseingang")
@Convert(converter = LocalDateTimeConverter.class)
private java.time.LocalDateTime incomingOrder;
@Column(name="Bearbeitungszeit")
@Convert(converter = DurationConverter.class)
private java.time.Duration timeOfRework;
}