QueryDSL is a nice library for producing typesafe, dynamic queries. It is a sane alternative to standard, hideous JPA Criteria API.
Spring Data provides a great way to create simple queries, but for more dynamic ones you have to go back to Criteria API or QueryDSL. Both are supported in Spring Data.
The problem
The most common use case of dynamic queries is filtering. So let’s say you have a list of blog Posts which have a bunch of properties and you want to filter it by all of them.
Project setup
The project is based on Spring Boot, Spring Data with H2 Database, QueryDSL, Lombok as mandatory lib and Spock with DbUnit for testing. You can find working example on my github.
First, let’s create our domain objects.
QueryDSL with Spring Data
Once everything is set up you can create repository extending QueryDslPredicateExecutor
It adds methods like Iterable<T> findAll(Predicate predicate); or Page<T> findAll(Predicate predicate, Pageable pageable); to fetch entities by QueryDSL Predicate.
Filtering
PostFilter
We want to filter out list by author, title, date range (not just exact day of post) and list of tags. Tags are represented by list of Strings as it would be prefered way to query from frontend.
Creating FilterBuilder - Take 1
Now we need a class to provide Predicate for given PostFilter that can later be used for findAll(Predicate predicate) method
Let’s face it. That looks like a lot of boilerplate… and it is a small example! Imagine you have 20 fields (which is not that uncommon).
Creating FilterBuilder - Take 2
We can leverage Java 8 method references and create a small fluent API.
and then use it like that
Not only it is more readable but alao it is flexible as OptionalBooleanBuilder accepts QueryDSL BooleanExpression which most filtering expressions used for filtering inherit from.
QueryDSL Web Support
If you are sending filter parameters from web (which you probably are) you can also use Spring QueryDSL Web Support. It looks neat for basic usage, but customization looks a bit cumbersome to me.
Testing
Does it even work? To check that, I created sample database state using DbUnit
and created Spring Integration Test using Spock and its wonderful support for Data Driven Testing
It can be run directly from IDE or by ./gradlew test
Conclusion
Java 8 once again simplified code by using very basic functional approach. This little trick can be used not only for constructing QueryDSL Predicates but also for minimizing “wall” of repeatable null checking and action on value.