11 votes

Comment spécifier le fuseau horaire UTC pour le timestamp de Spring Boot JPA ?

Environnement

  • Spring Boot Starter Data JPA 1.4.2
  • Eclipselink 2.5.0
  • Postgresql 9.4.1211.jre7

Problème

Je construis un microservice Spring Boot qui partage une base de données Postgresql avec un autre service. La base de données est initialisée en externe (hors de notre contrôle) et le type de colonne datetime utilisé par l'autre service est timestamp sans fuseau horaire . Par conséquent, puisque je veux que toutes les dates de la base de données aient le même type, ce type est une exigence pour les dates de mon entité JPA.

La façon dont je les mappe sur mes objets d'entité JPA est la suivante :

@Column(name = "some_date", nullable = false)
private Timestamp someDate;

Le problème est que lorsque je crée un Timestamp comme suit :

new java.sql.Timestamp(System.currentTimeMillis())

et je regarde la base de données, l'horodatage contient la date et l'heure de mon fuseau horaire local, mais je veux le stocker en UTC. En effet, mon fuseau horaire par défaut est défini sur "Europe/Bruxelles" et JPA/JDBC convertit mes données en UTC. java.sql.Timestamp dans mon fuseau horaire avant de le mettre dans la base de données.

Les solutions trouvées ne sont pas idéales

  • TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC")); a l'effet que je veux obtenir, mais il n'est pas adapté car il n'est pas spécifique à mon service. C'est-à-dire qu'elle affectera toute la JVM ou le thread actuel et ses enfants.

  • Démarrer l'application avec -Duser.timezone=GMT semble également faire le travail pour une instance unique d'une JVM en cours d'exécution. Il s'agit donc d'une meilleure solution que la précédente.

Mais existe-t-il un moyen de spécifier le fuseau horaire dans la configuration de JPA/datasource/spring boot ?

6voto

oizulain Points 121

La solution la plus appropriée que j'ai trouvée pour résoudre ce problème est d'utiliser un fichier AttributeConverter pour convertir Java 8 ZonedDateTime des objets dans java.sql.Timestamp afin qu'ils puissent être mis en correspondance avec les objets PostgreSQL timestamp without time zone type.

La raison pour laquelle vous avez besoin d'un AttributeConverter c'est parce que les types de date et d'heure de Java 8/Joda ne sont pas encore compatibles avec JPA .

En AttributeConverter ressemble à ça :

@Converter(autoApply = true)
public class ZonedDateTimeAttributeConverter implements AttributeConverter<ZonedDateTime, Timestamp> {

    @Override
    public Timestamp convertToDatabaseColumn(ZonedDateTime zonedDateTime) {
        return (zonedDateTime == null ? null : Timestamp.valueOf(zonedDateTime.toLocalDateTime()));
    }

    @Override
    public ZonedDateTime convertToEntityAttribute(Timestamp sqlTimestamp) {
        return (sqlTimestamp == null ? null : sqlTimestamp.toLocalDateTime().atZone(ZoneId.of("UTC")));
    }

}

Cela me permet de lire les horodatages de la base de données qui ne pas ont des informations sur le fuseau horaire comme ZonedDateTime qui ont le UTC le fuseau horaire. De cette façon, je conserve la date et l'heure exactes qui sont visibles sur la base de données. quel que soit le fuseau horaire dans lequel mon application fonctionne. .

Desde toLocalDateTime() applique également la conversion du fuseau horaire par défaut du système, cette AttributeConverter annule fondamentalement la conversion appliquée par le pilote JDBC.

Avez-vous vraiment besoin d'utiliser timestamp without timezone ?

En réalité, si vous stockez des informations relatives à la date et à l'heure sur un fuseau horaire (même s'il s'agit de l'UTC), la fonction timestamp without timezone Le type PostgreSQL est le mauvais choix. Le bon type de données à utiliser serait timestamp with timezone qui inclut l'information sur le fuseau horaire. Plus d'informations sur ce sujet aquí .

Cependant, si pour une raison quelconque, vous doit utiliser le timestamp without timezone Je pense que le ZonedDateTime L'approche ci-dessus constitue une solution robuste et cohérente.

Est-ce que vous sérialisez également le ZonedDateTime en JSON ?

Alors vous êtes probablement intéressé par le fait que vous avez besoin d'au moins la version 2.6.0 de la jackson-datatype-jsr310 pour que la sérialisation fonctionne. Plus d'informations à ce sujet dans cette réponse .

1voto

teppic Points 5304

Tu ne peux pas. Eclipselink utilise la version single arg de setTimestamp Le pilote postgresql jdbc ne permet pas de remplacer le fuseau horaire par défaut. Le pilote postgres propage même le fuseau horaire du client à la session, de sorte que les valeurs par défaut côté serveur ne vous seront d'aucune utilité non plus.

Vous pouvez essayer de contourner le problème, par exemple en écrivant un fichier JPA 2.1. AttributeConverter pour décaler vos horodatages dans la zone de destination, mais ils sont finalement voués à l'échec car le fuseau horaire de votre client est ajusté en fonction de l'heure d'été, ce qui rend certaines heures ambiguës ou non représentables.

Vous devrez définir le fuseau horaire par défaut sur votre client ou utiliser le langage SQL natif pour définir les timestamps sous forme de chaînes de caractères avec des casts.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X