web-dev-qa-db-fra.com

Comment spécifier un fuseau horaire UTC pour l'horodatage JPA Spring Boot

Environnement

  • Données de démarrage de démarrage à ressort 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 horodatage sans fuseau horaire . Par conséquent, comme je veux que toutes les dates de la base aient le même type, il est indispensable d’avoir ce type pour les dates de mon entité JPA.

La manière 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 horodatage comme suit: 

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

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

Solutions trouvées non idéales

  • TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC")); a l'effet que je souhaite obtenir, mais il ne convient pas car il n'est pas spécifique à mon service. C'est à dire. cela affectera l'ensemble de la JVM ou le thread actuel plus les enfants.

  • Le démarrage de l'application avec -Duser.timezone=GMT semble également effectuer le travail pour une seule instance d'une machine virtuelle Java en cours d'exécution. Par conséquent, une meilleure solution que la précédente.

Mais existe-t-il un moyen de spécifier le fuseau horaire dans la configuration de démarrage JPA/source de données/printemps?

7
oizulain

La solution la plus appropriée pour résoudre ce problème consiste à utiliser une variable AttributeConverter pour convertir des objets Java 8 ZonedDateTime en objets Java.sql.Timestamp, de sorte qu'ils puissent être mappés au type PostgreSQL timestamp without time zone

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

La AttributeConverter ressemble à ceci:

@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 n'ont pas d'informations de fuseau horaire comme objets ZonedDateTime ayant le fuseau horaire UTC. De cette façon, je garde la date exacte qui peut être vue sur la base de données indépendamment du fuseau horaire dans lequel mon application est exécutée dans

Etant donné que toLocalDateTime() applique également la conversion du fuseau horaire par défaut du système, cette AttributeConverter annule en principe 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 de date et heure sur un fuseau horaire (même s'il s'agit de l'heure UTC), le type timestamp without timezone PostgreSQL est le mauvais choix. Le bon type de données à utiliser serait timestamp with timezone, qui inclut les informations de fuseau horaire. Plus sur ce sujet ici .

Cependant, si pour une raison quelconque, vous devez utiliser le timestamp without timezone, j'estime que l'approche ZonedDateTime ci-dessus est une solution robuste et cohérente.

Etes-vous également en train de sérialiser ZonedDateTime en JSON?

Ensuite, vous êtes probablement intéressé par le fait qu'il vous faut au moins la version 2.6.0 de la dépendance jackson-datatype-jsr310 pour que la sérialisation fonctionne. Plus sur cela dans cette réponse .

6
oizulain

Tu ne peux pas. Eclipselink utilise la version arg unique de setTimestamp, déléguant la responsabilité de la gestion du fuseau horaire au pilote, et 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 sur la session. Par conséquent, les paramètres par défaut côté serveur ne vous seront d'aucune utilité.

Vous pouvez essayer de résoudre certains problèmes, par exemple, en écrivant un JPA 2.1 AttributeConverter pour déplacer vos horodatages dans la zone de destination, mais ils sont finalement condamnés car votre fuseau horaire client a des ajustements de l'heure avancée ou non représentable.

Vous devrez définir le fuseau horaire par défaut sur votre client ou passer dans SQL natif pour définir les horodatages comme des chaînes avec des conversions.

1
teppic