Dawid
Dawid

Reputation: 563

How do I filter data in a restful way using Spring?

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.

Upvotes: 27

Views: 42280

Answers (5)

Roberto Micheletti
Roberto Micheletti

Reputation: 1

(Disclosure: I am owner of the linked repo)

This is another alternative to RQSL is the following library: https://github.com/RobertoMike/Baradum

It will let you run search queries such as:

/search?name=Pepito&age=18,65&active=true

This library supports custom functions, searching over nested fields, enums, strings, numbers, booleans, dates, ...

Very simple usage:

@GetMapping(value = "/search")
public List<Entity> search() {
  
    return Baradum.make(User.class)
                .allowedFilters(
                        // This allows you to filter using like and add automatically the % at the end of the value
                        // Example "?name=ale" will be filtered as "name like 'ale%'"
                        new PartialFilter("name"),
                        // This allows you to filter by range
                        new IntervalFilter<>("age"),

                )
                .get();
}

No need to configure anything, just import the dependency:

<dependency>
    <groupId>io.github.robertomike</groupId>
    <artifactId>baradum-apache-tomcat</artifactId>
    <!--  For spring boot 2  -->
    <version>1.0.1</version>
    <!--  For spring boot 3  -->
    <version>2.0.1</version>
</dependency>

Upvotes: 0

naXa stands with Ukraine
naXa stands with Ukraine

Reputation: 37916

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.

Upvotes: 14

torshid
torshid

Reputation: 167

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.

Upvotes: 5

Haroldo_OK
Haroldo_OK

Reputation: 7230

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

Upvotes: 5

naXa stands with Ukraine
naXa stands with Ukraine

Reputation: 37916

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);
    }
}

Upvotes: 10

Related Questions