Je construis actuellement une API REST dans laquelle je souhaite que les clients filtrent facilement sur la plupart des propriétés d'une entité spécifique. Utilisation de QueryDSL en combinaison avec Spring Data REST ( un exemple d'Oliver Gierke ) me permet d'accéder facilement à 90% de ce que je veux en permettant aux clients de filtre en combinant des paramètres de requête qui font référence à des propriétés (par exemple /users?firstName=Dennis&lastName=Laumen
).
Je peux même personnaliser le mappage entre les paramètres de requête et les propriétés d'une entité en implémentant l'interface QuerydslBinderCustomizer
(par exemple pour les recherches non sensibles à la casse ou les correspondances de chaînes partielles). C'est très bien, mais je veux aussi que les clients puissent filtrer certains types en utilisant des plages. Par exemple, en ce qui concerne une propriété comme la date de naissance, je voudrais faire quelque chose comme ce qui suit, /users?dateOfBirthFrom=1981-1-1&dateOfBirthTo=1981-12-31
. Il en va de même pour les propriétés basées sur des nombres, /users?idFrom=100&idTo=200
. J'ai le sentiment que cela devrait être possible en utilisant l'interface QuerydslBinderCustomizer
mais l'intégration entre ces deux bibliothèques n'est pas très largement documentée.
En conclusion, est-ce possible en utilisant Spring Data REST et QueryDSL? Si oui, comment?
Je pense que vous devriez pouvoir faire fonctionner cela en utilisant la personnalisation suivante:
bindings.bind(user.dateOfBirth).all((path, value) -> {
Iterator<? extends LocalDate> it = value.iterator();
return path.between(it.next(), it.next());
});
La clé ici est d'utiliser ?dateOfBirth=…&dateOfBirth=
(Utilisez la propriété deux fois) et la liaison ….all(…)
qui vous donnera accès à toutes les valeurs fournies.
Assurez-vous d'ajouter l'annotation @DateTimeFormat
À la propriété dateOfBirth
- de User
pour que Spring puisse convertir le Strings
entrant en LocalDate
instances correctement.
Le lambda reçoit actuellement un Collection<? extends T>
Ce qui rend le démêlage des éléments individuels un peu plus pénible qu'il ne devrait l'être, mais je pense que nous pouvons changer cela dans une future version pour exposer plutôt un List
.
Comme il a été publié dans certains commentaires, j'ai également eu besoin d'avoir un comportement différent en fonction du nom de champ creationDateFrom
et creationDateTo
. Afin de le faire fonctionner, j'ai fait ce qui suit:
J'ai d'abord ajouté l'annotation @QueryEntity
Et deux autres champs à ma classe d'entité. Les champs ont été annotés avec:
@Transient
Afin que les champs ne soient pas conservés@Getter(value = AccessLevel.PRIVATE)
comme nous utilisons Lombok, l'annotation masque le champ du corps de la réponse@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
prend en charge le format d'analyse de la date sur le paramètre de requête url@QueryEntity
@Entity
public class MyEntity implements Serializable {
...
@Column(updatable = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
private Date creationDate;
@Transient
@Getter(value = AccessLevel.PRIVATE)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
private Date creationDateTo;
@Transient
@Getter(value = AccessLevel.PRIVATE)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
private Date creationDateFrom;
...
}
Ensuite, j'ai changé la façon de générer les classes querydsl de JPAAnnotationProcessor
à QuerydslAnnotationProcessor
. De cette façon, les champs annotés avec @Transient
Sont toujours générés sur QMyEntity
mais ne sont pas persistants. Configuration du plugin dans pom:
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/annotations</outputDirectory>
<processor>com.querydsl.apt.QuerydslAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
Enfin, j'ai étendu le QuerydslBinderCustomizer
et personnalisé les liaisons liées aux creationDateFrom
et creationDateTo
mais en appliquant la bonne logique sur creationDate
@Override
default void customize(QuerydslBindings bindings, QMyEntity root) {
bindings.bind(root.creationDateFrom).first((path, value) ->
root.creationDate.after(value));
bindings.bind(root.creationDateTo).first((path, value) ->
root.creationDate.before(value));
}
Avec tout cela, vous pouvez faire des requêtes de plage de dates en utilisant un, les deux ou aucun des critères:
http://localhost:8080/myentities?creation_date_to=2017-05-08
http://localhost:8080/myentities?creation_date_from=2017-01-01
http://localhost:8080/myentities?creation_date_from=2017-01-01&creation_date_to=2017-05-08
C'est ce que j'ai utilisé pour une liaison générique pour tous les champs de date, en attendant toujours 2 valeurs, de et vers.
bindings.bind(Date.class).all((final DateTimePath<Date> path, final Collection<? extends Date> values) -> {
final List<? extends Date> dates = new ArrayList<>(values);
Collections.sort(dates);
if (dates.size() == 2) {
return path.between(dates.get(0), dates.get(1));
}
throw new IllegalArgumentException("2 date params(from & to) expected for:" + path + " found:" + values);
});
C'est pour les champs datetime. Pour un champ de date, lors de l'obtention d'un seul paramètre, path.eq()
est logique, je suppose.