theodo-fintech / spring-search

Provides advanced search capabilities to a JPA entity
MIT License
193 stars 44 forks source link

Contributors Forks Stargazers Issues MIT License

codecov


Logo

Spring Search

Spring Search provides advanced search capabilities to a JPA entity
Explore the docs »

View Demo · Report Bug · Request Feature

Table of Contents

About The Project

Spring-search screenshot

Spring Search provides a simple query language to perform advanced searches for your JPA entities.

Let's say you manage cars, and you want to allow API consumers to search for:

You could either create custom repository methods for both these operations, which works well if you know in advance which fields users might want to perform searches on. You could also use spring-search that allows searching on all fields, combining logical operators and much more.

Please note that providing such a feature on your API does not come without risks such as performance issues and less clear capabilities for your API. This article summarizes these risks well.

Built With

Getting Started

Requirements : JDK 8 or more.
To get a local copy up and running follow these simple steps.

Installation

Maven

Add the repo to your project inside your pom.xml file

<dependency>
    <groupId>com.sipios</groupId>
    <artifactId>spring-search</artifactId>
    <version>0.2.6</version>
</dependency>
Gradle

Add the repo to your project by adding implementation 'com.sipios:spring-search:0.2.0' in your build.gradle file.

Usage

Your repository should be annotated as a RepositoryRestResource and should extend JpaSpecificationExecutor

import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.JpaSpecificationExecutor
import org.springframework.data.rest.core.annotation.RepositoryRestResource

@RepositoryRestResource
interface YourRepository : JpaRepository<YourModel, Int>, JpaSpecificationExecutor<YourModel>

Import the library in your controller

import com.sipios.springsearch.anotation.SearchSpec

Use it in your controller

@GetMapping("searchUrl")
fun yourFunctionNameHere(@SearchSpec specs: Specification<YourModel>): ResponseEntity<List<YourResponse>> {
    return ResponseEntity(yourRepository.findAll(Specification.where(specs)), HttpStatus.OK)
}

Operators

Operator Description Example
: Equal color:Red
! Not equal color!Red
> Greater than creationyear>2017
>: Greater than eq creationyear>2017
< Less than price<100000
<: Less than eq price<100000
* Starts with brand:*Martin
* Ends with brand:Aston*
* Contains brand:*Martin*
OR Logical OR color:Red OR color:Blue
AND Logical AND brand:Aston* AND price<300000
IN Value is in list color IN ['Red', 'Blue']
NOT IN Value is not in list color NOT IN ['Red', 'Blue']
IS EMPTY Collection field is empty cars IS EMPTY
IS NOT EMPTY Collection field is not empty cars IS NOT EMPTY
IS NULL Field is null brand IS NULL
IS NOT NULL Field is not null brand IS NOT NULL
() Parenthesis brand:Nissan OR (brand:Chevrolet AND color:Blue)
BETWEEN Value is between two values creationyear BETWEEN 2017 AND 2019
NOT BETWEEN Value is not between two values creationyear NOT BETWEEN 2017 AND 2019

Examples

  1. Using parenthesis
    Request : /cars?search=( brand:Nissan OR brand:Chevrolet ) AND color:Blue
    Note: Spaces inside the parenthesis are not necessary parenthesis example
  2. Using space in nouns
    Request : /cars?search=model:'Spacetourer Business Lounge'
    space example
  3. Using special characters
    Request: /cars?search=model:中华V7 special characters example
  4. Using deep fields
    Request : /cars?search=options.transmission:Auto deep field example
  5. Complex example
    Request : /cars?search=creationyear:2018 AND price<300000 AND (color:Yellow OR color:Blue) AND options.transmission:Auto complex example
  6. Using the BETWEEN operator Request : /cars?search=creationyear BETWEEN 2017 AND 2019

Blocking the search on a field

@GetMapping
public List<User> getUsers(@SearchSpec(blackListedFields = {"password"}) Specification<User> specs) {
  return userRepository.findAll(Specification.where(specs));
}

Troubleshooting

If you get the following error ⬇️

No primary or default constructor found for interface org.springframework.data.jpa.domain.Specification

You are free to opt for either of the two following solutions :

  1. Add a @Configuration class to add our argument resolver to your project

    // Kotlin
    @Configuration
    class SpringSearchResolverConf : WebMvcConfigurer {
    override fun addArgumentResolvers(argumentResolvers: MutableList<HandlerMethodArgumentResolver>) {
    argumentResolvers.add(SearchSpecificationResolver())
    }
    }
    // Java
    @Configuration
    public class SpringSearchResolverConf implements WebMvcConfigurer {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new SearchSpecificationResolver());
    }
    }
  2. Add a @ComponentScan annotation to your project configuration class

    @ComponentScan(basePackages = {"com.your-application", "com.sipios.springsearch"})

Roadmap

See the open issues for a list of proposed features (and known issues).

Please note that boolean parameter types are yet to be supported.

Contributing

Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated.

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

License

Distributed under the MIT License. See LICENSE for more information.

Contact

@sipios_fintech - contact@sipios.com

Project Link: https://github.com/sipios/spring-search