Spring et la JSR 310 (Date/Time)
Nov 10, 2017 · 625 words · 3 minutes read
Nous avons tous, rencontré un jour ou un autre, la problématique de stockage des dates et heures. L’API Date/Time de Java 8 nous a apporté (enfin) une API simple pour cela. Mais elle n’est pas prise en compte directement par Spring boot (1.X…). Ici, je vais vous montrer comment stocker en base des champ avec un des type de l’API Date/Time de java 8, et de l’exposer au format JSON. Le tout avec 2 dépendances supplémentaire et une annotation.
Sérialisation JSON
L’état des lieux
Par défaut, Spring-boot
version 1.5.8 (dernière release avant la version 2) embarque jackson grace à ces dependances :
mvn dependency:tree | grep jackson
[INFO] | +- com.fasterxml.jackson.core:jackson-databind:jar:2.8.10:compile
[INFO] | | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.8.0:compile
[INFO] | | \- com.fasterxml.jackson.core:jackson-core:jar:2.8.10:compile
Mais cette version de Jackson ne peut pas serialiser les dates, heure et timestamp de l’API Date/Time de Java 8 correctement, il renvoi la structure JSON de l’objet.
Prenons l’exemple d’un POJO embarquant une propriété de type LocalDateTime
:
public class ToDoItem {
private Long id;
private String description;
private LocalDateTime created;
}
Et voici sa sérialisation JSON obtenu depuis un endpoint au format JSON :
{
"created": {
"chronology": {
"calendarType": "iso8601",
"id": "ISO"
},
"dayOfMonth": 10,
"dayOfWeek": "FRIDAY",
"dayOfYear": 314,
"hour": 23,
"minute": 25,
"month": "NOVEMBER",
"monthValue": 11,
"nano": 424000000,
"second": 22,
"year": 2017
},
"description": "Description de l'item 10",
"id": 10
}
Nous avons bien notre date, mais jackson expose la sérialisation complete de l’objet LocalDateTime
, c’est precis, mais, il faut en convenir pas très exploitable…
Maitrisons un peu tout cela
Tout d’abord, je vais ajouter la dépendance Jackson qui permettra la possibilité de formatter les objets LocalDate
, LocalTime
et LocalDateTime
de la JSR 310 :
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
La version de jackson est fournie par le pom parent de SpringBoot avec la propriété
jackson.version
.
Puis je vais annoter la propriété afin de definit le formattage de la date :
public class ToDoItem {
...
@JsonFormat(
shape = JsonFormat.Shape.STRING, // (1)
pattern = "yyyyMMdd-HHmmSS" // (2)
)
private LocalDateTime created;
}
type de la representation
pattern de formattage de la date
Et voici le résultat de la sérialisation :
{
"created": "20171110-233774",
"description": "Description de l'item 10",
"id": 10
}
C’est quand même un peu plus standard, non ?
Sérialisation DB
Comme pour la sérialisation JSON, l’API Date/Time n’est pas prise en compte directement par Hbernate (qui est l’implementation JPA proposée par Spring).
Pour preuve, voici la génération du schéma via hibernate (classique) :
drop table to_do_item if exists;
create table to_do_item (
id bigint generated by default as identity (start with 1),
created varbinary(255),
description varchar(255), primary key (id)
);
Mon champ date/time (LocalDateTime
) est interprété comme varbinary
…
Pour qu’hibernate prenne en compte cette problématique, il faut simplement activer la prise en charge de la JSR 310 via une dépendance particulière :
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-java8</artifactId>
</dependency>
Et maintenant la génération du schéma via hibernate est correcte :
drop table to_do_item if exists;
create table to_do_item (
id bigint generated by default as identity (start with 1),
created timestamp,
description varchar(255), primary key (id)
);
Et cerise sur le gateau, plus la peine d’indiquer quelle type temporel nous attendons, hibernate l’interprete pour nous selon le type (de la JSR 310) du champs.
Exemples
Voici quelques exemple lié à l’execution des source afin de les tester.
Vous trouverez les sources sur mon repository : github.com::ptitbob/jsondateformat.
curl -X GET http://localhost:8080/todo-items -H 'cache-control: no-cache' -H 'content-type: application/json'
curl -X POST http://localhost:8080/todo-items -H 'cache-control: no-cache' -H 'content-type: application/json' -d '{"description":"test"}' -i
curl -X GET http://localhost:8080/todo-items/1 -H 'cache-control: no-cache' -H 'content-type: application/json' -i
curl -X PUT http://localhost:8080/todo-items/1 -H 'cache-control: no-cache' -H 'content-type: application/json' -i -d '{"id":1,"description":"test-22"}'
curl -X DELETE http://localhost:8080/todo-items/2 -H 'cache-control: no-cache' -H 'content-type: application/json' -i
have fun