Environnement
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?
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.
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.
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 .
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.