web-dev-qa-db-fra.com

Mise en veille prolongée avec Java 8 LocalDate & LocalDateTime dans la base de données

Mon exigence est de stocker toutes les dates et heures dans le fuseau horaire UTC dans la base de données. J'utilise Java 8's LocalDate & LocalDateTime dans mes entités Hibernate.

Est-ce correct, car LocalDate & LocalDateTime n'a pas de fuseau horaire associé?

Si ce n'est pas le cas, devrais-je retomber sur une bonne vieille (ou ancienne?) Date & Timestamp?

Ou dois-je utiliser Java 8's Instant? Si vous utilisez Instant, y aura-t-il une possibilité de stocker uniquement la partie date, sans heure?

La base de données est MySQL & SQL Server et il s'agit d'une application Spring Boot.

17
adi

Les types "Local…" n'ont délibérément aucun concept de fuseau horaire. Donc, ils représentent pas représentent un moment sur la timeline. Un LocalDateTime représente une vague gamme de moments possibles mais n'a aucune signification réelle jusqu'à l'attribution d'un décalage ou d'un fuseau horaire. Cela signifie appliquer un ZoneId pour obtenir un ZonedDateTime.

Par exemple, pour dire que Noël cette année commence au premier moment du 25 décembre, nous disons:

LocalDateTime ldt = LocalDateTime.of( 2017 , 12 , 25 , 0 , 0 , 0 , 0 );

Mais ce coup de minuit se produit plus tôt à l'est qu'à l'ouest.

C’est pourquoi le département Logistique des elfes trace la route du Père Noël à partir de Kiribati dans le Pacifique, le premier fuseau horaire du monde avec 14 heures d’avance sur UTC. Après avoir livré là-bas, ils acheminent Santa vers l'ouest vers des endroits comme la Nouvelle-Zélande pour minuit plus tard. Puis en Asie pour leur minuit plus tard. Puis l'Inde, et ainsi de suite, atteignant l'Europe pour leur minuit plusieurs heures plus tard, puis la côte est de l'Amérique du Nord pour leur minuit quelques heures plus tard. Tous ces endroits ont connu cet mêmeLocalDateTime à différents moments, chaque livraison étant représentée par un différentZonedDateTime objet.

Alors…

  • Si vous voulez enregistrer le concept de Noël commençant après minuit le 25, utilisez un LocalDateTime et écrivez dans une colonne de base de données de type TIMESTAMP WITHOUT TIME ZONE.
  • Si vous souhaitez enregistrer le moment exact de chaque livraison effectuée par le Père Noël, utilisez un ZonedDateTime et écrivez dans une colonne de base de données de type TIMESTAMP WITH TIME ZONE.

À propos de cette deuxième puce, sachez que presque tous les systèmes de base de données utiliseront les informations de zone pour ajuster la date-heure à UTC et stocker cette valeur UTC. Certains enregistrent également les informations de zone, mais certains tels que Postgres suppriment les informations de zone après les avoir utilisées pour s'ajuster en UTC. Donc "avec fuseau horaire" est quelque chose de mal approprié, ce qui signifie vraiment "avec respect pour le fuseau horaire". Si vous souhaitez vous souvenir de cette zone d'origine, vous devrez peut-être enregistrer son nom dans une colonne distincte à côté.

Une autre raison d'utiliser Local… types est pour les rendez-vous futurs. Les politiciens aiment souvent changer de fuseau horaire dans leur juridiction. Ils aiment adopter l'heure d'été (DST). De même pour changer les dates de leurs passages à l'heure d'été. Ils aiment abandonner leur adoption de l'heure d'été. Ils aiment redéfinir leurs fuseaux horaires, en changeant les frontières. Ils aiment redéfinir leur décalage par rapport à l'UTC parfois par des montants comme 15 minutes. Et ils donnent rarement un préavis, apportant de tels changements avec aussi peu qu'un mois ou deux d'avertissement.

Donc, pour prendre rendez-vous pour un examen médical pour l'année prochaine ou dans six mois, la définition du fuseau horaire ne peut pas être prévue. Donc, si vous voulez un rendez-vous à 9 h, vous devez utiliser un LocalTime ou LocalDateTime enregistré dans une colonne de base de données de type TIMESTAMP WITHOUT TIME ZONE. Sinon, ce rendez-vous à 9 h 00, s'il est zoné où le passage à l'heure d'été est reporté, peut apparaître à 8 h 00 ou 10 h 00.

Lors de la génération d'une planification projetée, vous pouvez appliquer un fuseau horaire (ZoneId) à ces valeurs "locales" (non zonées) pour créer des objets ZonedDateTime. Mais ne comptez pas sur ceux qui sont trop éloignés dans le temps où les politiciens peuvent ruiner leur sens en changeant de zone (s).

Conseil: Ces changements fréquents d'heure d'été et de fuseau horaire signifient que vous devez conserver votre fuseau horaire tzdata base de données à jour. Il y a un tzdata dans votre OS hôte, votre JVM, et peut-être dans votre système de base de données tel que Postgres. Les trois devraient être fréquemment mis à jour. Parfois, les zones changent plus rapidement que les cycles de mise à jour prévus pour ces produits, comme la Turquie, qui a décidé l'année dernière de rester sous l'heure d'été avec un préavis de plusieurs semaines seulement. Vous devrez donc parfois mettre à jour manuellement ces fichiers tzdata. Oracle fournit un outil pour mettre à jour les tzdata de leurs implémentations Java.

La meilleure pratique générale pour gérer les moments exacts est de les suivre en UTC. Appliquez un fuseau horaire uniquement lorsque cela est nécessaire, comme dans la présentation à un utilisateur où il s'attend à voir des valeurs dans son propre fuseau horaire paroissial. Dans Java.time, la classe Instant représente un moment dans la chronologie. En UTC avec une résolution de nanosecondes.

Instant instant = Instant.now() ;  // Current moment on the timeline in UTC.
ZonedDateTime zdt = instant.atZone( z ) ;  // Assign a time zone to view the same moment through the lens of a particular region’s wall-clock time.
Instant instant = zdt.toInstant();  // revert back to UTC, stripping away the time zone. But still the same moment in the timeline.

Soit dit en passant, les pilotes conformes à JDBC 4.2 et versions ultérieures peuvent traiter directement les types Java.time via:

  • PreparedStatement::setObject
  • ResultSet::getObject

Évitez les anciens types de données hérités tels que Java.util.Date et Java.sql.Timestamp dès que possible. Ils sont mal conçus, déroutants et imparfaits.

Comprenez que ces quatre éléments sont des représentations d'un moment sur la chronologie en UTC:

  • Moderne
    • Java.time.Instant
    • Java.time.OffsetDateTime avec un décalage attribué de ZoneOffset.UTC
  • Héritage
    • Java.util.Date
    • Java.sql.Timestamp

Si vous souhaitez une valeur de date uniquement sans heure et sans fuseau horaire, utilisez Java.time.LocalDate. Cette classe supplante Java.sql.Date.

En ce qui concerne les bases de données spécifiques, sachez que le standard SQL touche à peine au sujet des types date-heure et de leur gestion. De plus, les différentes bases de données varient considérablement, et je veux vraiment dire largement, dans leur prise en charge des fonctionnalités de date-heure. Certains n'ont pratiquement aucun soutien. Certains mélangent des types standard SQL avec des types propriétaires antérieurs aux types standard ou destinés à remplacer les types standard. En outre, les pilotes JDBC diffèrent dans leur comportement avec le marshaling des valeurs de date-heure vers/depuis la base de données. Assurez-vous d'étudier la documentation et la pratique, la pratique, la pratique.

Table of date-time types in Java (both legacy and modern) and in the SQL standard.

41
Basil Bourque

Ou devrais-je utiliser Java 8's Instant? Si vous utilisez Instant, y aura-t-il une possibilité de stocker uniquement la partie date, sans heure?

Instant devrait convenir à la plupart des opérations.

Ajoutez hibernate-Java8 À pom.xml Pour prendre en charge Java 8 fois l'API:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-Java8</artifactId>
    <version>${version.hibernate}</version>
</dependency>

Vous pouvez ensuite utiliser LocalDate ou LocalDateTime ou Instant pour les champs d'entité Hibernate. Vous devez supprimer @Temporal(TemporalType.TIMESTAMP).

Mon exigence est de stocker toutes les dates et heures dans le fuseau horaire UTC dans la base de données. J'utilise Java 8's LocalDate & LocalDateTime dans mes entités Hibernate.

Est-ce correct, car LocalDate et LocalDateTime n'ont pas de fuseau horaire associé?

Vous pouvez définir le fuseau horaire JVM par défaut quelque part dans un code de configuration:

@PostConstruct
void setUTCTimezone() {
    TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}

Ensuite, vous utiliserez l'heure UTC dans votre code.

Pour utiliser Java 8 types de date dans DTO, vous devez ajouter Jsr310JpaConverters :

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Et:

@EntityScan(basePackageClasses = { Application.class,    Jsr310JpaConverters.class })
SpringBootApplication
public class Application { … }

Plus d'options:

7
Justas