Bibliothèque pour créer et exécuter des requêtes avancées et dynamiques à l'aide de JPA dans Spring Boot.
Map<String, String>
, pour prendre en charge les points de terminaison GET avec les paramètres de requête.Grâce à jpa-search-helper, votre contrôleur* pourra recevoir des requêtes comme celle-ci :
Mode 1 :
curl -X GET
' https://myexampledomain.com/persons?
firstName=Biagio
&lastName_startsWith=Toz
&birthDate_gte=19910101
&country_in=IT,FR,DE
&address_eq=Via Roma 1,Via Milano/,1,20 West/,34th Street
&company.name_in=Microsoft,Apple,Google
&company.employees_between=500,5000 '
Mode 2 :
curl -X POST -H " Content-type: application/json " -d ' {
"filter" : {
"operator": "and", // the first filter must contain a root operator: AND, OR or NOT
"filters" : [
{
"operator": "eq",
"key": "firstName",
"value": "Biagio"
},
{
"operator": "or",
"filters": [
{
"operator": "startsWith",
"key": "lastName",
"value": "Toz",
"options": {
"ignoreCase": true
}
},
{
"operator": "endsWith",
"key": "lastName",
"value": "ZZI",
"options": {
"ignoreCase": true,
"trim" : true
}
}
]
},
{
"operator": "in",
"key": "company.name",
"values": ["Microsoft", "Apple", "Google"]
},
{
"operator": "or",
"filters": [
{
"operator": "gte",
"key": "birthDate",
"value": "19910101"
},
{
"operator": "lte",
"key": "birthDate",
"value": "20010101"
}
]
},
{
"operator": "between",
"key" : "company.employees",
"values": [500, 5000],
"options": {
"negate": true
}
}
]
},
"options": {
"pageSize": 10,
"pageOffset": 0,
"sortKey": "birthDate",
"sortDesc": false
}
} ' ' https://myexampledomain.com/persons '
..comment tu fais ? Lisez ce fichier Lisez-moi !
* Veuillez noter : la bibliothèque n'expose pas les contrôleurs/points de terminaison HTTP, mais propose uniquement le référentiel qui construira et exécutera les requêtes.
Aide à la recherche JPA | Botte de printemps | Java |
---|---|---|
[v0.0.1 - v2.1.1] | 3.2.x | [17 - 23] |
[v3.0.0 - v3.2.2] | 3.3.x | [17 - 23] |
[v3.3.0 - dernière] | 3.4.x | [17 - 23] |
<dependency>
<groupId>app.tozzi</groupId>
<artifactId>jpa-search-helper</artifactId>
<version>3.3.0</version>
</dependency>
implementation 'app.tozzi:jpa-search-helper:3.3.0
@Searchable
Commencez par appliquer l'annotation @Searchable
aux champs de votre modèle de domaine, ou bien de votre entité JPA, que vous souhaitez rendre disponible pour la recherche . Si vous souhaitez rendre les champs consultables dans d'autres objets, annotez-les avec @NestedSearchable
.
@ Data
public class Person {
@ Searchable
private String firstName ;
@ Searchable
private String lastName ;
@ Searchable ( entityFieldKey = "dateOfBirth" )
private Date birthDate ;
@ Searchable
private String country ;
@ Searchable
private String fillerOne ;
@ Searchable
private String fillerTwo ;
@ NestedSearchable
private Company company ;
@ Data
public static class Company {
@ Searchable ( entityFieldKey = "companyEntity.name" )
private String name ;
@ Searchable ( entityFieldKey = "companyEntity.employeesCount" )
private int employees ;
}
}
L'annotation permet de préciser :
Propriétés principales :
entityFieldKey
: le nom du champ défini sur le bean entité (à ne pas préciser si on utilise l'annotation sur le bean entité). Si elle n'est pas spécifiée, la clé sera le nom du champ.
targetType
: le type d'objet géré par entité. S'il n'est pas spécifié, la bibliothèque essaie de l'obtenir en fonction du type de champ (par exemple, un champ entier sans définition de type cible sera INTEGER
). S'il n'existe aucun type compatible avec ceux gérés, il sera géré sous forme de chaîne. Types gérés :
STRING
, INTEGER
, DOUBLE
, FLOAT
, LONG
, BIGDECIMAL
, BOOLEAN
, DATE
, LOCALDATE
, LOCALDATETIME
, LOCALTIME
, OFFSETDATETIME
, OFFSETTIME
, ZONEDDATETIME
.Propriétés de validation :
datePattern
: uniquement pour les types de cibles DATE
, LOCALDATE
, LOCALDATETIME
, LOCALTIME
, OFFSETDATETIME
, OFFSETTIME
, ZONEDDATETIME
. Définit le modèle de date à utiliser.maxSize, minSize
: longueur maximale/minimale de la valeur.maxDigits, minDigits
: uniquement pour les types numériques. Nombre maximum/minimum de chiffres.regexPattern
: modèle d'expression régulière.decimalFormat
: uniquement pour les types numériques décimaux. Défaut #.##
Autre:
sortable
: si faux, le champ peut être utilisé par la recherche mais ne peut pas être utilisé pour le tri. Par défaut : vrai.trim
: appliquer le trim.tags
: utile si le champ Modèle de Domaine peut correspondre à plusieurs champs d'entités (l'exemple est disponible plus bas).allowedFilters
: filtres exclusivement autorisés.notAllowedFilters
: filtres non autorisés.likeFilters
: filtres similaires autorisés ( contient , startWith , endWith ). Par défaut : vrai.En continuant l'exemple, nos classes d'entités :
@ Entity
@ Data
public class PersonEntity {
@ Id
private Long id ;
@ Column ( name = "FIRST_NAME" )
private String firstName ;
@ Column ( name = "LAST_NAME" )
private String lastName ;
@ Column ( name = "BIRTH_DATE" )
private Date dateOfBirth ;
@ Column ( name = "COUNTRY" )
private String country ;
@ Column ( name = "FIL_ONE" )
private String fillerOne ;
@ Column ( name = "FIL_TWO" )
private String fillerTwo ;
@ OneToOne
private CompanyEntity companyEntity ;
}
@ Entity
@ Data
public class CompanyEntity {
@ Id
private Long id ;
@ Column ( name = "NAME" )
private String name ;
@ Column ( name = "COUNT" )
private Integer employeesCount ;
}
JPASearchRepository
Votre référentiel Spring JPA doit étendre JPASearchRepository<YourEntityClass>
.
@ Repository
public interface PersonRepository extends JpaRepository < PersonEntity , Long >, JPASearchRepository < PersonEntity > {
}
Dans votre manager, ou dans votre service, ou partout où vous souhaitez utiliser le référentiel :
Mode 1 : définir une map <filter_key#options, value> :
// ...
@ Autowired
private PersonRepository personRepository ;
public List < Person > advancedSearch () {
// Pure example, in real use case it is expected that these filters can be passed directly by the controller
Map < String , String > filters = new HashMap <>();
filters . put ( "firstName_eq" , "Biagio" );
filters . put ( "lastName_startsWith#i" , "Toz" ); // ignore case
filters . put ( "birthDate_gte" , "19910101" );
filters . put ( "country_in" , "IT,FR,DE" );
filters . put ( "company.name_eq#n" , "Bad Company" ); // negation
filters . put ( "company.employees_between" , "500,5000" );
filters . put ( "fillerOne_null#n" , "true" ); // not null
filters . put ( "fillerTwo_empty" , "true" ); // empty
// Without pagination
List < PersonEntity > fullSearch = personRepository . findAll ( filters , Person . class );
filters . put ( "birthDate_sort" : "ASC" ); // sorting key and sorting order
filters . put ( "_limit" , "10" ); // page size
filters . put ( "_offset" , "0" ); // page offset
// With pagination
Page < PersonEntity > sortedAndPaginatedSearch = personRepository . findAllWithPaginationAndSorting ( filters , Person . class );
// ...
}
// ...
Mode 2 : au lieu d'une carte, vous devrez utiliser JPASearchInput
, présenté ici, pour plus de simplicité, au format JSON.
{
"filter" : {
"operator" : " and " , // the first filter must contain a root operator: AND, OR or NOT
"filters" : [
{
"operator" : " eq " ,
"key" : " firstName " ,
"value" : " Biagio "
},
{
"operator" : " or " ,
"filters" : [
{
"operator" : " startsWith " ,
"key" : " lastName " ,
"value" : " Toz " ,
"options" : {
"ignoreCase" : true
}
},
{
"operator" : " endsWith " ,
"key" : " lastName " ,
"value" : " ZZI " ,
"options" : {
"ignoreCase" : true ,
"trim" : true
}
}
]
},
{
"operator" : " in " ,
"key" : " company.name " ,
"values" : [ " Microsoft " , " Apple " , " Google " ]
},
{
"operator" : " or " ,
"filters" : [
{
"operator" : " gte " ,
"key" : " birthDate " ,
"value" : " 19910101 "
},
{
"operator" : " lte " ,
"key" : " birthDate " ,
"value" : " 20010101 "
}
]
},
{
"operator" : " empty " ,
"key" : " fillerOne " ,
"options" : {
"negate" : true
}
},
{
"operator" : " between " ,
"key" : " company.employees " ,
"values" : [ 500 , 5000 ],
"options" : {
"negate" : true
}
}
]
},
"options" : {
"pageSize" : 10 ,
"pageOffset" : 0 ,
"sortKey" : " birthDate " ,
"sortDesc" : false
}
}
Grâce au mode 2, il est possible de gérer des filtres complexes avec AND
, OR
et NOT
(voir plus loin).
InvalidFieldException
.InvalidValueException
.JPASearchException
Nom du filtre | Clé de bibliothèque | Modes pris en charge |
---|---|---|
ET | et | 1, 2 |
OU | ou | 2 |
PAS | pas | 2 |
Grâce au Mode 1 , tous les filtres composent exclusivement une recherche AND
.
Pour utiliser les autres opérateurs OR
et NOT
, vous devez utiliser le mode 2
Nom du filtre | Clé de bibliothèque | SQL | Modes pris en charge | Valeur requise |
---|---|---|---|---|
Égal | équip | sql_col = val | 1,2 | Oui |
Contient | contient | sql_col LIKE '%val%' | 1,2 | Oui |
Dans | dans | sql_col IN (val1, val2, ..., valN) | 1,2 | Oui |
Commence par | commenceAvec | sql_col LIKE 'val%' | 1,2 | Oui |
Se termine par | se termine par | sql_col LIKE '%val' | 1,2 | Oui |
Supérieur à | GT | sql_col > val | 1,2 | Oui |
Supérieur ou égal | gite | sql_col >= val | 1,2 | Oui |
Moins que | lt | sql_col <val | 1,2 | Oui |
Inférieur ou égal | LTE | sql_col <= val | 1,2 | Oui |
Entre | entre | sql_col ENTRE val1 ET val2 | 1,2 | Oui |
Nul | nul | sql_col EST NULL | 1,2 | Non |
Vide | vide | sql_collection_col EST NULL | 1,2 | Non |
Mode 1
Description des options | Clé de bibliothèque |
---|---|
Ignorer le cas | #je |
Négation | #n |
Garniture | #t |
Les touches d'option doivent être ajoutées au filtre ; par exemple ?firstName_eq#i=Biagio ou ?firstName_eq#i#n=Biagio
Mode 2
Description des options | Clé de bibliothèque (attributs Java) |
---|---|
Ignorer le cas | ignoreCase |
Négation | nier |
Garniture | garniture |
Pour chaque filtre il est possible de définir options
{
// ...
{
"operator" : " eq " ,
"key" : " firstName " ,
"value" : " Biagio " ,
"options" : {
"ignoreCase" : true ,
"trim" : false ,
"negate" : true
}
}
// ...
}
Objet Java :
@ Data
public static class JPASearchFilterOptions {
private boolean ignoreCase ;
private boolean trim ;
private boolean negate ;
}
Nom du filtre | Clé | Valeurs fixes |
---|---|---|
Limite (taille de la page) | limite | |
Décalage (numéro de page) | compenser | |
Trier | trier | ASC, DESC |
Mode 1 : par exemple ?firstName_sort=DESC&_limit=10&_offset=0
Mode 2 : options
racine de valeur :
{
"filter" : {
// ...
},
"options" : {
"sortKey" : " firstName " ,
"sortDesc" : true ,
"pageSize" : 10 ,
"pageOffset" : 1
}
}
Objet Java :
@ Data
public static class JPASearchOptions {
private String sortKey ;
private Boolean sortDesc = false ;
private Integer pageSize ;
private Integer pageOffset ;
private List < String > selections ;
}
,
: par exemple ?myField_in=test1,test2 --> valeurs à rechercher : [" test1 ", " test2 "]/,
: par exemple ?myField_in=test1,test2/,test3 --> valeurs à rechercher : [" test1 ", " test2,test3 "] @Projectable
Commencez par appliquer l'annotation @Projectable
aux champs de votre modèle de domaine, ou alternativement à votre entité JPA, que vous souhaitez rendre disponible pour la sélection . Si vous souhaitez rendre sélectionnables des champs dans d'autres objets, annotez-les avec @NestedProjectable
.
@ Data
public class Person {
@ Searchable
private String firstName ;
@ Projectable
@ Searchable
private String lastName ;
@ Projectable ( entityFieldKey = "dateOfBirth" )
@ Searchable ( entityFieldKey = "dateOfBirth" )
private Date birthDate ;
@ Searchable
private String country ;
@ Searchable
private String fillerOne ;
@ Searchable
private String fillerTwo ;
@ NestedProjectable
@ NestedSearchable
private Company company ;
@ Data
public static class Company {
@ Searchable ( entityFieldKey = "companyEntity.name" )
private String name ;
@ Projectable ( entityFieldKey = "companyEntity.employeesCount" )
@ Searchable ( entityFieldKey = "companyEntity.employeesCount" )
private int employees ;
}
}
L'annotation permet de préciser :
Propriétés principales :
entityFieldKey
: le nom du champ défini sur le bean entité (à ne pas préciser si on utilise l'annotation sur le bean entité). Si elle n'est pas spécifiée, la clé sera le nom du champ.JPASearchRepository
Votre référentiel Spring JPA doit étendre JPAProjectionRepository<YourEntityClass>
.
@ Repository
public interface PersonRepository extends JpaRepository < PersonEntity , Long >, JPASearchRepository < PersonEntity >, JPAProjectionRepository < PersonEntity > {
}
Dans votre manager, ou dans votre service, ou partout où vous souhaitez utiliser le référentiel :
Mode 1 : définir (ou ajouter à la carte utilisée pour la recherche Mode 1) une carte :
,
// ...
@ Autowired
private PersonRepository personRepository ;
public List < Person > advancedSearch () {
// Pure example, in real use case it is expected that these filters can be passed directly by the controller
Map < String , String > filters = new HashMap <>();
filters . put ( "firstName_eq" , "Biagio" );
filters . put ( "lastName_startsWith#i" , "Toz" ); // ignore case
filters . put ( "birthDate_gte" , "19910101" );
filters . put ( "country_in" , "IT,FR,DE" );
filters . put ( "company.name_eq#n" , "Bad Company" ); // negation
filters . put ( "company.employees_between" , "500,5000" );
filters . put ( "fillerOne_null#n" , "true" ); // not null
filters . put ( "fillerTwo_empty" , "true" ); // empty
// Selections
filters . put ( "selections" , "lastName,birthDate,company.employees" );
// Without sorting
List < Map < String , Object >> result = personRepository . projection ( filters , Person . class , PersonEntity . class );
filters . put ( "birthDate_sort" : "ASC" ); // sorting key and sorting order
// With sorting
List < Map < String , Object >> sortedAndPaginatedSearch = personRepository . projectionWithSorting ( filters , Person . class , PersonEntity . class );
// ... convert the list of maps into your model
}
// ...
Mode 2 : au lieu d'une carte, vous devrez utiliser JPASearchInput
, présenté ici, pour plus de simplicité, au format JSON.
{
"filter" : {
"operator" : " and " , // the first filter must contain a root operator: AND, OR or NOT
"filters" : [
{
"operator" : " eq " ,
"key" : " firstName " ,
"value" : " Biagio "
},
{
"operator" : " or " ,
"filters" : [
{
"operator" : " startsWith " ,
"key" : " lastName " ,
"value" : " Toz " ,
"options" : {
"ignoreCase" : true
}
},
{
"operator" : " endsWith " ,
"key" : " lastName " ,
"value" : " ZZI " ,
"options" : {
"ignoreCase" : true ,
"trim" : true
}
}
]
},
{
"operator" : " in " ,
"key" : " company.name " ,
"values" : [ " Microsoft " , " Apple " , " Google " ]
},
{
"operator" : " or " ,
"filters" : [
{
"operator" : " gte " ,
"key" : " birthDate " ,
"value" : " 19910101 "
},
{
"operator" : " lte " ,
"key" : " birthDate " ,
"value" : " 20010101 "
}
]
},
{
"operator" : " empty " ,
"key" : " fillerOne " ,
"options" : {
"negate" : true
}
},
{
"operator" : " between " ,
"key" : " company.employees " ,
"values" : [ 500 , 5000 ],
"options" : {
"negate" : true
}
}
]
},
"options" : {
"pageSize" : 10 ,
"pageOffset" : 0 ,
"sortKey" : " birthDate " ,
"sortDesc" : false ,
"selections" : [
" lastName " ,
" birthDate " ,
" company.employees "
]
}
}
Pour les deux modes, la projection renverra un résultat List<Map<String, Object>> où la structure de la carte et les clés refléteront la structure de l'entité (pour être clair, toJson(entityList) == toJson(mapList) )
Remarque 1 :
Attention : la projection par défaut force toutes les relations Join à LEFT JOIN. Si vous ne souhaitez pas ce comportement, choisissez d'utiliser les méthodes du référentiel (méthodes avec le suffixe 'Classic') qui permettent de modifier éventuellement uniquement les relations que vous souhaitez modifier.
Remarque 2 :
La projection, que vous le vouliez ou non, extraira toujours les champs qui représentent les clés primaires d'une entité (ou d'entités associées).
Remarque 3 :
La pagination n'est pas prise en charge
InvalidFieldException
.JPASearchException
Il est possible de forcer les jointures avec fetch pour permettre à Hibernate (ou à votre framework JPA) d'exécuter une seule requête pour les relations définies sur l'entité. Ceci n'est possible que sans pagination :
// ...
Map < String , JoinFetch > fetches = Map . of ( "companyEntity" , JoinFetch . LEFT );
personRepository . findAll ( filters , Person . class , fetches );
// ...
Si vous disposez d'un Modèle de Domaine qui est le résultat de la conversion de plusieurs entités, il est possible de spécifier explicitement une mappe (string, string) dont la clé représente le nom du champ Modèle de Domaine et la valeur est le nom du champ de l'entité à rechercher :
// ...
Map < String , String > entityFieldMap = Map . of ( "company" , "companyEntity.name" );
// Without pagination
personRepository . findAll ( filters , Person . class , fetches , entityFieldMap );
// With pagination
personRepository . findAllWithPaginationAndSorting ( filters , Person . class , entityFieldMap );
// ...
Un autre cas particulier pourrait être celui où un objet peut être répété dans le modèle de domaine pour représenter plusieurs éléments de l'entité. La solution pour la recherche :
@ Entity
public class CoupleEntity {
@ Id
private Long id ;
@ Column ( name = "p1_fn" )
private String p1FirstName ;
@ Column ( name = "p1_ln" )
private String p1LastName ;
@ Column ( name = "p2_fn" )
private String p2FirstName ;
@ Column ( name = "p2_ln" )
private String p2LastName ;
}
@ Data
public class Couple {
@ NestedSearchable
private Person p1 ;
@ NestedSearchable
private Person p2 ;
@ Data
public static class Person {
@ Searchable ( tags = {
@ Tag ( fieldKey = "p1.firstName" , entityFieldKey = "p1FirstName" ),
@ Tag ( fieldKey = "p2.firstName" , entityFieldKey = "p2FirstName" ),
})
private String firstName ;
@ Searchable ( tags = {
@ Tag ( fieldKey = "p1.lastName" , entityFieldKey = "p1LastName" ),
@ Tag ( fieldKey = "p2.lastName" , entityFieldKey = "p2LastName" ),
})
private String lastName ;
}
}
curl - request GET
- url ' https://www.myexampledomain.com/couples?
p1.firstName_iEq=Romeo
&p2.firstName_iEq=Giulietta '
Attention : cette bibliothèque n'expose aucun point de terminaison et donc aucun contrôleur. Un exemple de projet, exhaustif et complet, est disponible ici.
Contrôleur:
@ RestController
@ RequestMapping ( "/persons" )
public class PersonController {
@ Autowired
private PersonManager personManager ;
@ GetMapping ( produces = MediaType . APPLICATION_JSON_VALUE )
public List < Person > findPersons ( @ RequestParam Map < String , String > requestParams ) {
return personManager . find ( requestParams );
}
@ GetMapping ( path = "/projection" , produces = MediaType . APPLICATION_JSON_VALUE )
public List < Person > projection ( @ RequestParam Map < String , String > requestParams ) {
return personManager . projection ( requestParams );
}
}
Bean service/gestionnaire :
@ Service
public class PersonManager {
@ Autowired
private PersonRepository personRepository ;
public List < Person > find ( Map < String , String > filters ) {
return personRepository . findAllWithPaginationAndSorting ( filters , Person . class ). stream (). map ( this :: toModel ). toList ();
}
public List < Person > projection ( Map < String , String > filters ) {
return personRepository . projection ( filters , Person . class , PersonEntity . class ). stream (). map ( this :: toModel ). toList ();
}
private static Person toModel ( PersonEntity personEntity ) {
// ...
}
private static Person toModel ( Map < String , Object > entityMap ) {
// ...
}
}
Boucle:
curl - X GET
' http://localhost:8080/persons?
firstName=Biagio
&lastName_startsWith=Toz
&birthDate_gte=19910101
&country_in=IT,FR,DE
&company.name_in=Microsoft,Apple
&company.employees_between=500,5000 '
ou
curl - X GET
' http://localhost:8080/persons/projection?
firstName=Biagio
&lastName_startsWith=Toz
&birthDate_gte=19910101
&country_in=IT,FR,DE
&company.name_in=Microsoft,Apple
&company.employees_between=500,5000
&selections=firstName,birthDate '
Contrôleur:
@ RestController
@ RequestMapping ( "/persons" )
@ Validated
public class PersonController {
@ Autowired
private PersonManager personManager ;
@ PostMapping ( produces = MediaType . APPLICATION_JSON_VALUE , consumes = MediaType . APPLICATION_JSON_VALUE )
public List < Person > findPersons ( @ Valid @ RequestBody JPASearchInput input ) {
return personManager . find ( input );
}
}
@ PostMapping ( path = "/projection" , produces = MediaType . APPLICATION_JSON_VALUE , consumes = MediaType . APPLICATION_JSON_VALUE )
public List < Person > projection ( @ Valid @ RequestBody JPASearchInput input ) {
return personManager . projection ( input );
}
}
Bean service/gestionnaire :
@ Service
public class PersonManager {
@ Autowired
private PersonRepository personRepository ;
public List < Person > find ( JPASearchInput input ) {
return personRepository . findAllWithPaginationAndSorting ( input , Person . class ). stream (). map ( this :: toModel ). toList ();
}
public List < Person > find ( JPASearchInput input ) {
return personRepository . projection ( input , Person . class , PersonEntity . class ). stream (). map ( this :: toModel ). toList ();
}
private static Person toModel ( PersonEntity entity ) {
// ...
}
private static Person toModel ( Map < String , Object > entityMap ) {
// ...
}
}
Boucle:
curl -X POST -H " Content-type: application/json " -d ' {
"filter" : {
"operator": "and", // the first filter must contain a root operator: AND, OR or NOT
"filters" : [
{
"operator": "eq",
"key": "firstName",
"value": "Biagio"
},
{
"operator": "or",
"filters": [
{
"operator": "startsWith",
"key": "lastName",
"value": "Toz",
"options": {
"ignoreCase": true
}
},
{
"operator": "endsWith",
"key": "lastName",
"value": "ZZI",
"options": {
"ignoreCase": true,
"trim" : true
}
}
]
},
{
"operator": "in",
"key": "company.name",
"values": ["Microsoft", "Apple", "Google"]
},
{
"operator": "or",
"filters": [
{
"operator": "gte",
"key": "birthDate",
"value": "19910101"
},
{
"operator": "lte",
"key": "birthDate",
"value": "20010101"
}
]
},
{
"operator": "between",
"key" : "company.employees",
"values": [500, 5000],
"options": {
"negate": true
}
}
]
},
"options": {
"pageSize": 10,
"pageOffset": 0,
"sortKey": "birthDate",
"sortDesc": false
}
} ' ' http://localhost:8080/persons '
ou
curl -X POST -H " Content-type: application/json " -d ' {
"filter" : {
"operator": "and", // the first filter must contain a root operator: AND, OR or NOT
"filters" : [
{
"operator": "eq",
"key": "firstName",
"value": "Biagio"
},
{
"operator": "or",
"filters": [
{
"operator": "startsWith",
"key": "lastName",
"value": "Toz",
"options": {
"ignoreCase": true
}
},
{
"operator": "endsWith",
"key": "lastName",
"value": "ZZI",
"options": {
"ignoreCase": true,
"trim" : true
}
}
]
},
{
"operator": "in",
"key": "company.name",
"values": ["Microsoft", "Apple", "Google"]
},
{
"operator": "or",
"filters": [
{
"operator": "gte",
"key": "birthDate",
"value": "19910101"
},
{
"operator": "lte",
"key": "birthDate",
"value": "20010101"
}
]
},
{
"operator": "between",
"key" : "company.employees",
"values": [500, 5000],
"options": {
"negate": true
}
}
]
},
"options": {
"sortKey": "birthDate",
"sortDesc": false,
"selections" : [
"birthDate",
"firstName",
"lastName"
]
}
} ' ' http://localhost:8080/persons/projection '