How to easy implement a kind of 'REST API query language' with Spring Data to filter the entities?
For example, for the following Person
entity:
@Data
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
private LocalDate dob; // date of birth
private String name;
@Formula("timestampdiff('year', dob, now())")
private Integer age;
public Person(String name, LocalDate dob) {
this.name = name;
this.dob = dob;
}
}
I would like to get its data with such a request:
GET /people?name=jo&age=18&page=1&sort=name,desc
I.e.: 'get the 1st page of all people whose name
contains "jo" (case insensitive) and whose age
is equal to 18, sorting by name
in descending order'.
With help of Querydsl Web Support, the part of Web support Spring Data extension, we can easy implement a kind of 'REST API query language' to filter our entities.
All we need is do the following:
1) extend our repository from QuerydslPredicateExecutor
,
2) add Predicate
with annotation @QuerydslPredicate
, as argument, to our REST controller method
3) use this predicate in findAll
method of the repository:
public interface PersonRepo extends JpaRepository<Person, Long>, QuerydslPredicateExecutor<Person> {
}
@RequiredArgsConstructor
@RestController
@RequestMapping("/people")
public class PersonController {
private final PersonRepo personRepo;
@GetMapping
public ResponseEntity getFiltered(@QuerydslPredicate(root = Person.class) Predicate predicate, Pageable pageable) {
return ResponseEntity.ok(personRepo.findAll(predicate, pageable)));
}
}
Then we will be able to request our data:
GET /people?name=John&age=18&page=1&sort=name,desc
Next we have to make case insensitive 'like' filter. To do this we extend our repo from QuerydslBinderCustomizer
and override its customize
method (right in the repo):
public interface PersonRepo extends
JpaRepository<Person, Long>,
QuerydslPredicateExecutor<Person>,
QuerydslBinderCustomizer<QPerson> {
@Override
default void customize(QuerydslBindings bindings, QPerson person) {
// Make case-insensitive 'like' filter for all string properties
bindings.bind(String.class).first((SingleValueBinding<StringPath, String>) StringExpression::containsIgnoreCase);
}
}
To make it works we have to add parameter bindings
to @QuerydslPredicate
of our controller method:
@GetMapping
public ResponseEntity getFiltered(
@QuerydslPredicate(root = Person.class, bindings = PersonRepo.class) Predicate predicate,
Pageable pageable
) {
return ResponseEntity.ok(personRepo.findAll(predicate, pageable)));
}
Now we can request our data as asked in the question:
GET /people?name=jo&age=18&page=1&sort=name,desc
With QuerydslBinderCustomizer
we can implement more complex filters, for example between
and greater or equal
filters (add this code to customize
method):
bindings.bind(person.age).all((path, value) -> {
Iterator<? extends Integer> it = value.iterator();
Integer from = it.next();
if (value.size() >= 2) {
Integer to = it.next();
return Optional.of(path.between(from, to)); // between
} else {
return Optional.of(path.goe(from)); // greater or equal
}
});
If we specify two age
parameters in the request then we get all records with the age between these parameters. If we specify only one age
parameter - we get records with the age is greater or equal that value.
GET /people?age=18&age=30
... get all people with the age between 18 and 30
GET /people?age=18
... get all people with the age is greater or equal than 18
In the end we can exclude some unnecessary properties from the filter, for example the entity id
(add this code to customize
method):
bindings.excluding(person.id);
To use Querydsl Web Support we have to add these dependencies and plugin to our Spring Boot project:
<dependencies>
<!-- ... -->
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- ... -->
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/annotations</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Then, it's important, compile the project to build 'Q-classes' of our entities.
Full example demo you can find in my repo: sb-querydsl-sd-demo, and Postman API-docs of this demo - here: REST query language with Querydsl and Spring Data.