Библиотека для создания и выполнения сложных и динамических запросов с использованием JPA в Spring Boot.
Map<String, String>
для поддержки конечных точек GET с параметрами запроса.Через jpa-search-helper ваш контроллер* сможет получать такие запросы:
Режим 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 '
Режим 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 '
..как ты это делаешь? Прочтите этот файл readme!
* Обратите внимание: библиотека не предоставляет контроллеры/конечные точки HTTP, а предлагает только репозиторий, который будет создавать и выполнять запросы.
Помощник по поиску JPA | Весенние ботинки | Ява |
---|---|---|
[v0.0.1 - v2.1.1] | 3.2.х | [17 - 23] |
[v3.0.0 - v3.2.2] | 3.3.х | [17 - 23] |
[v3.3.0 - последняя] | 3.4.х | [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
Начните с применения аннотации @Searchable
к полям вашей модели предметной области или, альтернативно, вашей сущности JPA, которые вы хотите сделать доступными для поиска . Если у вас есть поля, которые вы хотите сделать доступными для поиска в других объектах, добавьте к ним аннотацию @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 ;
}
}
Аннотация позволяет указать:
Основные свойства:
entityFieldKey
: имя поля, определенного в объектном компоненте (не указывать, если используется аннотация в объектном компоненте). Если не указано, ключом будет имя поля.
targetType
: тип управляемого объекта по сущности. Если не указано, библиотека пытается получить его на основе типа поля (например, целочисленное поле без определения целевого типа будет INTEGER
). Если не существует типа, совместимого с управляемыми, он будет управляться как строка. Управляемые типы:
STRING
, INTEGER
, DOUBLE
, FLOAT
, LONG
, BIGDECIMAL
, BOOLEAN
, DATE
, LOCALDATE
, LOCALDATETIME
, LOCALTIME
, OFFSETDATETIME
, OFFSETTIME
, ZONEDDATETIME
.Свойства проверки:
datePattern
: только для целевых типов DATE
, LOCALDATE
, LOCALDATETIME
, LOCALTIME
, OFFSETDATETIME
, OFFSETTIME
, ZONEDDATETIME
. Определяет используемый шаблон даты.maxSize, minSize
: максимальная/минимальная длина значения.maxDigits, minDigits
: только для числовых типов. Максимальное/минимальное количество цифр.regexPattern
: шаблон регулярного выражения.decimalFormat
: только для десятичных числовых типов. По умолчанию #.##
Другой:
sortable
: если false, поле можно использовать для поиска, но нельзя использовать для сортировки. По умолчанию: правда.trim
: применить обрезку.tags
: полезно, если поле модели предметной области может соответствовать нескольким полям сущности (пример доступен ниже).allowedFilters
: исключительно разрешенные фильтры.notAllowedFilters
: не разрешенные фильтры.likeFilters
: разрешены подобные фильтры ( содержит , BeginsWith , EndsWith ). По умолчанию: правда.Продолжая пример, наши классы сущностей:
@ 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
Ваш репозиторий Spring JPA должен расширять JPASearchRepository<YourEntityClass>
.
@ Repository
public interface PersonRepository extends JpaRepository < PersonEntity , Long >, JPASearchRepository < PersonEntity > {
}
В вашем менеджере, или в вашем сервисе, или там, где вы хотите использовать репозиторий:
Режим 1 : определить карту <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 );
// ...
}
// ...
Режим 2 : вместо карты вам нужно будет использовать JPASearchInput
, показанный здесь для простоты, в формате 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
}
}
В режиме 2 можно управлять сложными фильтрами с помощью AND
, OR
и NOT
(см. ниже).
InvalidFieldException
.InvalidValueException
.JPASearchException
Название фильтра | Ключ от библиотеки | Поддерживаемые режимы |
---|---|---|
И | и | 1, 2 |
ИЛИ | или | 2 |
НЕТ | нет | 2 |
В режиме 1 все фильтры составляют исключительно поиск AND
.
Чтобы использовать другие операторы OR
и NOT
, вы должны использовать режим 2.
Название фильтра | Ключ от библиотеки | SQL | Поддерживаемые режимы | Требуемое значение |
---|---|---|---|---|
Равно | экв. | sql_col = значение | 1,2 | да |
Содержит | содержит | sql_col LIKE '%val%' | 1,2 | да |
В | в | sql_col IN (значение1, значение2, ..., значениеN) | 1,2 | да |
Начинается с | начинается с | sql_col LIKE 'val%' | 1,2 | да |
Заканчивается на | заканчивается с | sql_col НРАВИТСЯ '%val' | 1,2 | да |
Больше, чем | гт | sql_col > значение | 1,2 | да |
Больше или равно | гте | sql_col >= значение | 1,2 | да |
Меньше, чем | лт | sql_col <значение | 1,2 | да |
Меньше или равно | лте | sql_col <= значение | 1,2 | да |
Между | между | sql_col МЕЖДУ val1 И val2 | 1,2 | да |
Нулевой | нулевой | sql_col имеет значение NULL | 1,2 | нет |
Пустой | пустой | sql_collection_col имеет значение NULL | 1,2 | нет |
Режим 1
Описание опции | Ключ от библиотеки |
---|---|
Игнорировать регистр | #я |
Отрицание | #н |
Подрезать | #т |
Ключи опций должны быть добавлены к фильтру; например ?firstName_eq#i=Бьяджо или ?firstName_eq#i#n=Бьяджо
Режим 2
Описание опции | Ключ библиотеки (атрибуты Java) |
---|---|
Игнорировать регистр | игнорировать регистр |
Отрицание | отрицать |
Подрезать | подрезать |
Для каждого фильтра можно определить options
{
// ...
{
"operator" : " eq " ,
"key" : " firstName " ,
"value" : " Biagio " ,
"options" : {
"ignoreCase" : true ,
"trim" : false ,
"negate" : true
}
}
// ...
}
Java-объект:
@ Data
public static class JPASearchFilterOptions {
private boolean ignoreCase ;
private boolean trim ;
private boolean negate ;
}
Название фильтра | Ключ | Фиксированные значения |
---|---|---|
Ограничение (размер страницы) | предел | |
Смещение (номер страницы) | компенсировать | |
Сортировать | сортировать | ВОС, УБЫВАНИЕ |
Режим 1 : например ?firstName_sort=DESC&_limit=10&_offset=0
Режим 2 : значения корневых options
:
{
"filter" : {
// ...
},
"options" : {
"sortKey" : " firstName " ,
"sortDesc" : true ,
"pageSize" : 10 ,
"pageOffset" : 1
}
}
Java-объект:
@ Data
public static class JPASearchOptions {
private String sortKey ;
private Boolean sortDesc = false ;
private Integer pageSize ;
private Integer pageOffset ;
private List < String > selections ;
}
,
: например ?myField_in=test1,test2 --> значения для поиска: [" test1 ", " test2 "]/,
: например ?myField_in=test1,test2/,test3 --> значения для поиска: [" test1 ", " test2,test3 "] @Projectable
аннотация Начните с применения аннотации @Projectable
к полям вашей модели предметной области или, альтернативно, вашей сущности JPA, которые вы хотите сделать доступными для выбора . Если у вас есть поля, которые вы хотите сделать доступными для выбора в других объектах, добавьте к ним аннотацию @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 ;
}
}
Аннотация позволяет указать:
Основные свойства:
entityFieldKey
: имя поля, определенного в объектном компоненте (не указывать, если используется аннотация в объектном компоненте). Если не указано, ключом будет имя поля.JPASearchRepository
Ваш репозиторий Spring JPA должен расширять JPAProjectionRepository<YourEntityClass>
.
@ Repository
public interface PersonRepository extends JpaRepository < PersonEntity , Long >, JPASearchRepository < PersonEntity >, JPAProjectionRepository < PersonEntity > {
}
В вашем менеджере, или в вашем сервисе, или там, где вы хотите использовать репозиторий:
Режим 1 : определите (или добавьте к карте, используемой для поиска в режиме 1) карту:
,
// ...
@ 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
}
// ...
Режим 2 : вместо карты вам нужно будет использовать JPASearchInput
, показанный здесь для простоты, в формате 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 "
]
}
}
Для обоих режимов проекция вернет результат List<Map<String, Object>>, где структура карты и ключи будут отражать структуру объекта (чтобы было понятно toJson(entityList) == toJson(mapList) ).
Примечание 1:
Будьте осторожны: проекция по умолчанию заставляет все отношения соединения работать как LEFT JOIN. Если вы не хотите такого поведения, выберите использование методов репозитория (методы с суффиксом «Классический»), которые позволяют вам изменять только те отношения, которые вы хотите изменить.
Примечание 2:
Проекция, независимо от того, хотите вы этого или нет, всегда будет извлекать поля, представляющие первичные ключи сущности (или связанных сущностей).
Примечание 3:
Пагинация не поддерживается
InvalidFieldException
.JPASearchException
Можно принудительно выполнить соединения с помощью выборки, чтобы Hibernate (или ваша платформа JPA) выполнила один запрос для отношений, определенных в объекте. Это возможно только без нумерации страниц :
// ...
Map < String , JoinFetch > fetches = Map . of ( "companyEntity" , JoinFetch . LEFT );
personRepository . findAll ( filters , Person . class , fetches );
// ...
Если у вас есть модель предметной области, которая является результатом преобразования нескольких сущностей, можно явно указать карту (строку, строку), ключ которой представляет имя поля модели предметной области, а значение — имя поля сущность, которую нужно искать:
// ...
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 );
// ...
Другим особым случаем может быть случай, когда объект может повторяться в модели предметной области для представления нескольких частей сущности. Решение для поиска:
@ 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 '
Обратите внимание: эта библиотека не предоставляет никаких конечных точек и, следовательно, контроллеров. Пример проекта, исчерпывающий и полный, доступен здесь.
Контроллер:
@ 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 );
}
}
Компонент службы/менеджера:
@ 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 ) {
// ...
}
}
Завиток:
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 '
или
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 '
Контроллер:
@ 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 );
}
}
Компонент службы/менеджера:
@ 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 ) {
// ...
}
}
Завиток:
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 '
или
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 '