Can Spring Data REST's QueryDSL integration be used to perform more complex queries?
I think you should be able to get this to work using the following customization:
bindings.bind(user.dateOfBirth).all((path, value) -> { Iterator<? extends LocalDate> it = value.iterator(); return path.between(it.next(), it.next());});
The key here is to use ?dateOfBirth=…&dateOfBirth=
(use the property twice) and the ….all(…)
binding which will give you access to all values provided.
Make sure you add the @DateTimeFormat
annotation to the dateOfBirth
-property of User
so that Spring is able to convert the incoming Strings
into LocalDate
instances correctly.
The lambda currently gets a Collection<? extends T>
which makes untangling the individual elements a bit more pain that it needs to be, but I think we can change this in a future release to rather expose a List
.
As it was posted in some comment I also had the need to have different behaviour according to the field name creationDateFrom
and creationDateTo
. In order to make it work I did the following:
First I added the @QueryEntity
annotation and two more fields to my entity class. The fields were annotated with:
@Transient
so the fields are not persisted@Getter(value =AccessLevel.PRIVATE)
as we are using Lombok, the annotation hides thefield from the response body@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
takes care of the format for parsing thedate on the url query parameter
@QueryEntity@Entitypublic 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; ...}
Then I changed the way of generating the querydsl classes from JPAAnnotationProcessor
to QuerydslAnnotationProcessor
. This way fields annotated with @Transient
are still generated on QMyEntity
but are not persisted. Plugin configuration in 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>
Finally I extended the QuerydslBinderCustomizer
and customized the bindings related with the creationDateFrom
and creationDateTo
but applying the right logic over creationDate
@Overridedefault 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));}
With all of this you can do date range queries using one, both or none of the criterias:
http://localhost:8080/myentities?creation_date_to=2017-05-08http://localhost:8080/myentities?creation_date_from=2017-01-01http://localhost:8080/myentities?creation_date_from=2017-01-01&creation_date_to=2017-05-08
This is what I used for a generic binding for all date fields, always expecting 2 values, from and to.
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);});
This is for datetime fields. For a date field, when getting a single parameter, path.eq()
makes sense I guess.