The new Certification Class of REST With Spring is out:

>> CHECK OUT THE COURSE

1. Overview

In this quick tutorial, we’ll discuss the Spring Data Querydsl Web Support.

This is definitely an interesting alternative to all the other ways we focused on in the main REST Query Language series.

2. The Maven Config

First, let’s start with our maven configuration:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.0.RELEASE</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-commons</artifactId>
    </dependency>
    <dependency>
        <groupId>com.mysema.querydsl</groupId>
        <artifactId>querydsl-apt</artifactId>
        <version>${querydsl.version}</version>
    </dependency>
    <dependency>
        <groupId>com.mysema.querydsl</groupId>
        <artifactId>querydsl-jpa</artifactId>
        <version>${querydsl.version}</version>
    </dependency>
...

Note that Querydsl web support is available in spring-data-commons since 1.11

3. The User Repository

Next, let’s take a look at our repository:

public interface UserRepository extends 
  JpaRepository<User, Long>, QueryDslPredicateExecutor<User>, QuerydslBinderCustomizer<QUser> {
    @Override
    default public void customize(QuerydslBindings bindings, QUser root) {
        bindings.bind(String.class).first(
          (StringPath path, String value) -> path.containsIgnoreCase(value));
        bindings.excluding(root.email);
    }
}

Note that:

  • We’re overriding QuerydslBinderCustomizer customize() to customize the default binding
  • We’re customizing the default equals binding to ignore case for all String properties
  • We’re also excluding the user’s email from Predicate resolution

Check out the full documentation here.

4. The User Controller

Now, let’s take a look at the controller:

@RequestMapping(method = RequestMethod.GET, value = "/users")
@ResponseBody
public Iterable<User> findAllByWebQuerydsl(
  @QuerydslPredicate(root = User.class) Predicate predicate) {
    return userRepository.findAll(predicate);
}

This is the interesting part – notice how we’re obtaining a Predicate directly out of the HttpRequest, using the @QuerydslPredicate annotation.

Here’s how a URL with this type of query would look like:

http://localhost:8080/users?firstName=john

And here’s how a potential response would be structure:

[
   {
      "id":1,
      "firstName":"john",
      "lastName":"doe",
      "email":"[email protected]",
      "age":11
   }
]

5. Live Test

Finally, let’s test out the new Querydsl Web Support:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
public class UserLiveTest {

    private ObjectMapper mapper = new ObjectMapper();
    private User userJohn = new User("john", "doe", "[email protected]");
    private User userTom = new User("tom", "doe", "[email protected]");

    private static boolean setupDataCreated = false;

    @Before
    public void setupData() throws JsonProcessingException {
        if (!setupDataCreated) {
            givenAuth().contentType(MediaType.APPLICATION_JSON_VALUE)
                       .body(mapper.writeValueAsString(userJohn))
                       .post("http://localhost:8080/users");
 
            givenAuth().contentType(MediaType.APPLICATION_JSON_VALUE)
                       .body(mapper.writeValueAsString(userTom))
                       .post("http://localhost:8080/users");
            setupDataCreated = true;
        }
    }

    private RequestSpecification givenAuth() {
        return RestAssured.given().auth().preemptive().basic("user1", "user1Pass");
    }
}

First, let’s get all users in the system:

@Test
public void whenGettingListOfUsers_thenCorrect() {
    Response response = givenAuth().get("http://localhost:8080/users");
    User[] result = response.as(User[].class);
    assertEquals(result.length, 2);
}

Next, let’s find users by first name:

@Test
public void givenFirstName_whenGettingListOfUsers_thenCorrect() {
    Response response = givenAuth().get("http://localhost:8080/users?firstName=john");
    User[] result = response.as(User[].class);
    assertEquals(result.length, 1);
    assertEquals(result[0].getEmail(), userJohn.getEmail());
}

Next, lest find users by partial last name:

@Test
public void givenPartialLastName_whenGettingListOfUsers_thenCorrect() {
    Response response = givenAuth().get("http://localhost:8080/users?lastName=do");
    User[] result = response.as(User[].class);
    assertEquals(result.length, 2);
}

Now, let’s try to find users by email:

@Test
public void givenEmail_whenGettingListOfUsers_thenIgnored() {
    Response response = givenAuth().get("http://localhost:8080/users?email=john");
    User[] result = response.as(User[].class);
    assertEquals(result.length, 2);
}

Note: When we try to find user by email – the query was ignored, because we excluded user’s email from Predicate resolution.

6. Conclusion

In this article we had a quick intro to the Spring Data Querydsl Web Support and a cool, simple way to obtain a Predicate directly out of the HTTP request and using that to retrieve data.

Go deeper into building a REST API with Spring:

>> CHECK OUT THE CLASSES

  • Pete

    So simple…dead simple…love it..

  • Bananenbrot1990

    I love it too.
    But is it also possible to implement an OR condition like “http://localhost:8080/users?firstName=dolastName=do” to get a Predicate like “lower(user.firstName) = do || lower(user.lastName) = do”?

    • Glad you like the solution,
      To answer your question – as long as the URL is valid, yes, it’s perfectly fine to change the Query Language. The syntax about the language isn’t about right or wrong, it’s simply about making a decision and then providing a consistent QL based on that.
      So, with regard to the way to represent an “or” – yes, there are several valid ways to do it.

      Hope that helps.
      Cheers,
      Eugen.

      • Vicky

        Hi Eugen, Can you please let me know for the or expression example with Query DSL web support.

        • Hey Vicky – as I replied earlier in this thread – you can of course implement new operations on top of the baseline query language developed in this series.
          I didn’t add an or operation to keep things simple and easy to understand – feel free to add one of course.
          Cheers,
          Eugen.

          • Vicky

            Thanks for the response but I am still stuck on implementing the OR condition for URL http://localhost:8080/users?firstName=john&lastName=Ken
            It would be great if you can help me with steps to override the default behavior of AND operation with Predicate.

          • Vicky

            I was thinking to override the QuerydslPredicateBuilder class and change the default Boolean behavior, please let me know if there is a better & easy solution.

          • Well, I can add it to our Content Calendar if it’s not urgent. That means that it will likely be picked up by an author and be out in a couple of months.
            Let me know if that helps. Cheers,
            Eugen.

          • Vicky

            If you can help me with the steps it would really help me a lot as this needs to be implemented quickly.

          • Yeah, I figured that might be the case. Unfortunately I personally don’t have time for it (focused on the courses) so the best I can do is add the topic to our content calendar.
            Cheers,
            Eugen.

          • Vicky

            I am merely interested in the steps for the implementation not the actual implementation so if that would not take any time, please share. thanks so much

          • Well Vicky, I would approach this in the same way I approached the rest of the Query Language.
            First, I would define the syntax at the URL level – to see how an OR should be represented. Second, I would work that into the parser so that you can extract the query and parse that into the in-memory structure we’re using.
            Finally, I would map that to the persistence layer.
            And of course I suggest writing plenty of live tests hitting the API with scenarios using the new operation.
            Hope that helps. Cheers,
            Eugen.

          • Vicky

            Thanks Eugen, as per the custom parser means that we are not able to utilise existing Query DSL web support in Controller as @QuerydslPredicate(root = User.class) Predicate predicate. Is there a way in utilising the existing Query DSL web support and change the Predicate boolean operation in controller?

            If only the way to build custom Predicate Builder for OR boolean operation to be used instead of and operation, then could you please provide steps for this.

          • You’re right Vicky – I was thinking of the main QL series but this writeup is actually outside of that series, so you don’t need to parse things out yourself.

            With those other solutions the flow would be as I described it here – so simple, but a bit manual.
            With the web support out of QueryDSL here – I’m not sure, I haven’t looked into whether or not it supports OR.
            But, even if it does – keep in mind that a pre-build solution like this will always be less flexible than the more manual approach I was describing in my earlier comment here.

            Hope that points you in the right direction. Cheers,

            Eugen.