Skip to content

Instantly share code, notes, and snippets.

@odrotbohm
Last active October 22, 2021 09:08
Show Gist options
  • Save odrotbohm/decf03d4948cd58a51bc to your computer and use it in GitHub Desktop.
Save odrotbohm/decf03d4948cd58a51bc to your computer and use it in GitHub Desktop.
Dynamic, Querydsl-based filter bindings using Spring Data REST

We’re just experimenting with some Spring Data / Spring MVC integration that will allow you to bind property path expressions in the request to a Querydsl Predicate as a Spring MVC controller method argument. This is cool for manually implemented controllers so I wondered what it takes to integrate that with Spring Data REST.

In the example above you see StoreRepository extending QuerydslPredicateExecutor<Store>. In the coming version of Spring Data REST, this will cause the collection resource exposed for the repository to accept property based filtering:

GET /stores?name=Foo

will return all stores with a name of "Foo". A plain equals(…) comparison might be a bit strict, so we expose a QuerydslBinderCustomizer interface that will allow you to customize the way properties are bound within the overall predicate.

As you can see above, the easiest way to define customized bindings is by implementing customize(…) in a default method and use the provided QuerydslBindings and entity path to tweak the binding as you like. You see we define the city to be bound via endsWith(…), String properties in general are bound via a contains(…) to make the resulting predicate less restrictive.

A working example can be found in this feature branch of the Spring Data Examples repository.

public interface StoreRepository extends PagingAndSortingRepository<Store, String>,
QueryDslPredicateExecutor<Store>, QuerydslBinderCustomizer<QStore> {
@RestResource(rel = "by-location")
Page<Store> findByAddressLocationNear(Point location, Distance distance, Pageable pageable);
default void customize(QuerydslBindings bindings, QStore store) {
bindings.bind(store.address.city).single((path, value) -> path.startsWith(value));
bindings.bind(String.class).single((StringPath path, String value) -> path.contains(value));
}
}
@ArslanAnjum
Copy link

why cant we define a binding for 3rd and so on child in bindings.bind.? for example I have defined following binding :

bindings.bind(survey.district).first(
				(path,value)->	survey
								.location
								.mauza
								.patwarCircle
								.qanungoiHalqa
								.tehsil
								.district
								.districtId
								.eq(value)
								);

but this doesnt seem to work and a get an error that no property district.districtId found.

@aycanadalwork
Copy link

How to do "or" search anyone?

@Ahulkv
Copy link

Ahulkv commented Nov 25, 2017

Is it possible to searialize/deserialize the predicate containing complex objects to send it over the request ?

@frosiere
Copy link

Wouldn't it make more sense to follow the same principle as the other search methods by having something like

GET /stores/search/findAll?name=Foo
GET /stores/search/findAll?name=Foo&status=ACTIVE
or
GET /stores/search/findWithFilter?name=Foo

@qyfbq123
Copy link

qyfbq123 commented Jul 2, 2020

The above comment has code that works nicely for string values, but what about numeric? If I wanted a query string (for Spring Data REST) that translated to a greater-than or less-than operation, I could not include string characters in the value. Can a single path have multiple aliases that resolve to different query operations. For example:

/api/people?age=30 
/api/people?ageGreaterThan=30  
/api/people?ageBetween=30,50  

I accomplish numberic range search like date between search, just as @jihlee replied.

bindings.bind(people.age).all((path, values) -> {
            Iterator<? extends Integer> it = values.iterator();
            if(values.size() == 1) {
                Integer v = it.next();
                return Optional.of(path.eq(v));
            } else {
                Integer v = it.next();
                Integer sign = it.next();
                switch (sign) {
                    case 0:
                        return Optional.of(path.eq(v));
                    case -1:
                        return Optional.of(path.lt(v));
                    case -2:
                        return Optional.of(path.loe(v));
                    case 1:
                        return Optional.of(path.gt(v));
                    case 2:
                        return Optional.of(path.goe(v));
                }
            }
            return Optional.empty();
        });

and search people like these:

GET /api/people?age=18&age=0  //people who's age is 18
GET /api/people?age=18&age=-1 //people who's age less than 18
GET /api/people?age=18&age=1 //people who's age greater than 18

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment