Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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));
}
}
@hrandika

This comment has been minimized.

Copy link

hrandika commented Sep 12, 2015

Where is the 'QStore' comes from? Is this works only with MongoDB? any clue to get this work with mysql or similar databases?

@wwadge

This comment has been minimized.

Copy link

wwadge commented Sep 15, 2015

@hrandika QStore comes from queryDSL and is generated automatically if you set it up correctly

@hrandika

This comment has been minimized.

Copy link

hrandika commented Oct 14, 2015

@wwadge. Thanks got it working after setting up correctly.

@hrandika

This comment has been minimized.

Copy link

hrandika commented Nov 8, 2015

Can we have other examples (if they are implemented or available)? Like date between,Search form list or set within a entity?

@pwachira

This comment has been minimized.

Copy link

pwachira commented Feb 19, 2016

This looks to be a godsend....

@zandrewitte

This comment has been minimized.

Copy link

zandrewitte commented Aug 12, 2016

@hrandika, Have you gotten any feedback or found a way to do a multiple value query, like a date between search?

@jihlee

This comment has been minimized.

Copy link

jihlee commented Aug 27, 2016

@hrandika, @zandrewitte, have a look at the following thread.
http://stackoverflow.com/a/35158320/4477227

The answer was written by Oliver and it shows how to accomplish something like date between search.

@fernandodof

This comment has been minimized.

Copy link

fernandodof commented Sep 20, 2016

Hello everyone. I have an entity which I want to search with contains and sometimes with containsIngonoreCase. Is there a way to change this dynamically ?

default void customize(QuerydslBindings bindings, Quser user) {
    //change based on request
    bindings.bind(user.name).first((path, value) -> path.contains(value));
    //OR
    bindings.bind(user.name).first((path, value) -> path.containsIgnoreCase(value));
}

///UPDATED (Answering my own question above)

bindings.bind(String.class).first((StringPath path, String value) -> {
       if (value.startsWith("%") && value.endsWith("%")) {
           return path.containsIgnoreCase(value.substring(1, value.length() - 1));
       } else {
           return path.eq(value);
       }
});
@woemler

This comment has been minimized.

Copy link

woemler commented Jan 16, 2017

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  
@fernandodof

This comment has been minimized.

Copy link

fernandodof commented Jan 18, 2017

Good question @woemler

@Ramblurr

This comment has been minimized.

Copy link

Ramblurr commented Feb 27, 2017

Indeed, I'd love to know the answer to that question @woemler.

Similarly, I'm using a Projection to create a "virtual" field aggregate on a class, and I want to be able to filter on that virtual field. But of course that virtual field doesn't appear in the querydsl Q class, so I can't bind it using this API, even though I can trivially express the predicate as a querydsl boolean expression.

Perhaps @olivergierke will see this gist again.

@woemler

This comment has been minimized.

Copy link

woemler commented Mar 9, 2017

That is an interesting problem, @Ramblurr. Is it possible to create entity classes that represent database views, rather than tables? That might be a workaround to your problem.

@KhaledLela

This comment has been minimized.

Copy link

KhaledLela commented May 12, 2017

If Store has a collection of products
/api/store?products.productName=Sony%20Vaio // Returns Stores that has Sony Viao on it's products Good, But all products returned,
I need Only product with name Sony Viao.
default void customize(QuerydslBindings bindings, QStore store) { bindings.bind(store.products.any().first((path, value) -> path.equals(value)); }
Stackoverflow
Any help ?

@ZhongjunTian

This comment has been minimized.

Copy link

ZhongjunTian commented Jun 9, 2017

Hi
I have a good implementation of JPA Specification which could be much more powerful than you guys need.
You can also filter data by ANY complex logic (and, or) and ANY operations (equal, startswith, endswith, greaterthan...)
But you just need write ZERO line of code.
https://github.com/ZhongjunTian/spring-repository-plus/

@ArslanAnjum

This comment has been minimized.

Copy link

ArslanAnjum commented Oct 5, 2017

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

This comment has been minimized.

Copy link

aycanadalwork commented Nov 13, 2017

How to do "or" search anyone?

@Ahulkv

This comment has been minimized.

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

This comment has been minimized.

Copy link

frosiere commented May 16, 2018

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.