26

As the title says.

I basically would love to do requests like

/api/todos/?completed=eq.true&created_at=lt.1486462109399

Is there any ready spring way of achieving such? Something akin to the Page/Pageable mechanism would be great.

If there is none I think I could implement it using Hibernate Criteria Queries & Argument Re-solvers. Basically allowing me to write my controllers like

 @GetMapping
 public ResponseEntity<Page<TodoDTO>> listAll(Criteria criteria, Pageable pageable) 
 {
        Page<Todo> todos = todoService.listAll(criteria, pageable)
        ...
 }

A custom Argument resolver would be responsible for turning the query string into a Criteria. Not quite sure yet how I would handle it within the service but that's the direction in which I would try to implement this.

Would that be a good approach? Any recommendations? (All assuming there are no ready mechanism for such already).

Your help is much appreciated.

naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259
Dawid
  • 543
  • 1
  • 4
  • 10
  • 1
    Last time I had the same problem I couldn't find a spring only solution. However you can check how to achieve this by using Fiql/ rsql with Here is a good [blog]( http://www.baeldung.com/rest-api-search-language-rsql-fiql) on that. – gkatzioura Feb 07 '17 at 15:08
  • It's been a year since you asked this question and I don't know where you're at but this should answer your problem: http://www.baeldung.com/rest-api-search-language-spring-data-specifications You can also check this question too: https://stackoverflow.com/questions/20280708/filtering-database-rows-with-spring-data-jpa-and-spring-mvc – leventunver Feb 21 '18 at 13:21

4 Answers4

14

Another option to build a fluent query API is to use a RSQL parser. RSQL is a query language for parametrized filtering of entries in RESTful APIs. Follow this article and your API would be able to handle URLs like:

http://localhost:8080/users?search=firstName==jo*;age<25

Sample controller:

@RestController
@RequestMapping(value = "/users")
public class UserController {

    @Autowired
    private UserRepository repo;

    @GetMapping
    public List<User> findAllByRsql(@RequestParam(value = "search") String search) {
        Node rootNode = new RSQLParser().parse(search);
        Specification<User> spec = rootNode.accept(new CustomRsqlVisitor<User>());
        return repo.findAll(spec);
    }

}

Gradle dependency:

// https://mvnrepository.com/artifact/cz.jirutka.rsql/rsql-parser
implementation("cz.jirutka.rsql:rsql-parser:2.1.0")

Recommendations from the library author:

I recommend to use separate URI query parameter for sorting, e.g. ?query=name==Flynn&sortBy=name, not to mix it with RSQL expression. The same applies to paging; I recommend URI parameters named limit and offset.

naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259
10

You can build a Search/Filter REST API using Spring Data JPA and Specifications. Here is a test URL example that the resulting API would be able to handle:

http://localhost:8080/users?search=lastName:doe,age>25

and example controller:

@RestController
@RequestMapping(value = "/users")
public class UserController {

    @Autowired
    private UserRepository repo;

    @GetMapping
    public List<User> search(@RequestParam(value = "search") String search) {
        UserSpecificationsBuilder builder = new UserSpecificationsBuilder();
        Pattern pattern = Pattern.compile("(\w+?)(:|<|>)(\w+?),");
        Matcher matcher = pattern.matcher(search + ",");
        while (matcher.find()) {
            builder.with(matcher.group(1), matcher.group(2), matcher.group(3));
        }

        Specification<User> spec = builder.build();
        return repo.findAll(spec);
    }
}
naXa stands with Ukraine
  • 35,493
  • 19
  • 190
  • 259
  • This is the correct answer. Also mentioned by leventunver in a comment on the OP's question. – java-addict301 Mar 15 '19 at 16:02
  • What is UserSpecificationBuilder ? – Nimo1981 May 21 '20 at 11:05
  • @Nimo1981 in my answer I embedded only a concise example of a controller to showcase the proposed solution. To find more details about the solution follow the link in my answer and read Baeldung's article. Or examine the full implementation on GitHub -> https://github.com/eugenp/tutorials/tree/master/spring-rest-query-language (search by `UserSpecificationsBuilder` keyword if it's the only thing you are looking for). – naXa stands with Ukraine May 21 '20 at 12:06
  • @naXa the github link in the comment is dead – christopher.online Mar 04 '21 at 11:39
  • 1
    @chriswillow there's a new link at the end of the Baeldung's article -> https://github.com/eugenp/tutorials/tree/master/spring-web-modules/spring-rest-query-language – naXa stands with Ukraine Mar 04 '21 at 12:39
4

You may also want to try Spring Search: https://github.com/sipios/spring-search

Example: /cars?search=creationyear:2018 AND price<300000 AND (color:Yellow OR color:Blue) AND options.transmission:Auto

enter image description here

Haroldo_OK
  • 6,612
  • 3
  • 43
  • 80
4

As a good alternative to RQSL, you may use the following library: https://github.com/turkraft/spring-filter

It will let you run search queries such as:

/search?filter= average(ratings) > 4.5 and brand.name in ('audi', 'land rover') and (year > 2018 or km < 50000) and color : 'white' and accidents is empty

As you see in the example, it supports functions, searching over nested fields, nested logics, enums, strings, numbers, booleans, dates, ...

Very simple usage:

@GetMapping(value = "/search")
public Page<Entity> search(@Filter Specification<Entity> spec, Pageable page) {
  return repo.findAll(spec, page);
}

No need to configure anything, just import the dependency:

<dependency>
    <groupId>com.turkraft</groupId>
    <artifactId>spring-filter</artifactId>
    <version>1.0.5</version>
</dependency>

There are also examples using MongoDB.

torshid
  • 137
  • 1
  • 9