Querity
Open-source Java query builder for SQL and NoSQL.
Querity is a powerful and extensible query builder for Java applications, designed to simplify database queries across different technologies.
With Querity, you can effortlessly build REST APIs that support filtering, sorting, and pagination — perfect for displaying data in frontend applications (see Demo).
- Users benefit from a simple textual query language to interact with data.
- Developers can leverage a fluent Java API to construct complex queries with ease.
Querity supports both SQL and NoSQL databases through modular components — import only what you need for your project. Currently, it works with any SQL database (via JPA), MongoDB, and Elasticsearch.
Why choose Querity?
✔ Learn once, use everywhere – a unified approach to querying data.
✔ Database-agnostic queries – filtering, sorting, and pagination work across different databases.
✔ Fluent Java API – construct powerful queries with clean, readable code.
✔ Simple textual query language – ideal for REST API integration.
✔ Consistent API design – expose the same REST endpoints across all projects.
✔ Easy database switching – migrate between databases without rewriting business logic.
Requirements
- Java 17+
- For the Spring modules:
- Spring Framework 6 (optionally Spring Boot 3… makes things a lot simpler)
Installing
All releases are published to the Maven Central repository (see here).
Install one of the modules in your project as follows (see Available modules).
Maven:
<dependency>
<groupId>io.github.queritylib</groupId>
<artifactId>querity-spring-data-jpa</artifactId>
<version>3.6.0</version>
</dependency>
Gradle:
implementation "io.github.queritylib:querity-spring-data-jpa:3.6.0"
See Releases to check the latest version and see the changelogs.
All the Spring-related modules are “Spring Boot starters”: if you use Spring Boot you just need to add the dependency to your project and start using it, no other configuration needed.
Without Spring Boot autoconfiguration
You can use Querity without Spring Boot, but you need to configure the Querity bean in your Spring configuration.
Example with JPA:
import io.github.queritylib.querity.api.Querity;
@Configuration
public class QuerityConfiguration {
@Bean
Querity querity(EntityManager entityManager) {
return new QuerityJpaImpl(entityManager);
}
}
You can also disable the autoconfiguration of the Spring Boot starter by adding the following property to your application.properties:
querity.autoconfigure.enabled=false
This is useful if you want to import multiple Querity modules (e.g. JPA and Elasticsearch) and configure multiple Querity beans in your application.
Demo
Check out the simplest demo application using Querity at querity-demo.
Available modules
Currently, Querity supports the following technologies with its modules:
The datasource configuration is not managed by Querity and is delegated to the underlying application.
querity-spring-data-jpa
Supports Spring Data JPA and any SQL database with a compatible JDBC driver.
querity-jpa
Supports plain Jakarta Persistence API and any SQL database with a compatible JDBC driver.
This module is not Spring-specific, so it’s not a Spring Boot starter: you will need to apply all the configurations manually.
You basically need to instantiate a QuerityJpaImpl object and pass it the JPA EntityManager you want to use.
querity-spring-data-mongodb
Supports Spring Data MongoDB.
querity-spring-data-elasticsearch
Supports Spring Data Elasticsearch.
Remember to map the fields you want to query as “keyword” in your Elasticsearch index.
querity-spring-web
Supports JSON serialization and deserialization of Querity objects in Spring Web MVC.
With this module, you can pass a JSON Query or Condition object as request param in your Spring @RestController and it will be automatically deserialized into a Querity object.
For example, your API calls will look like this:
curl 'http://localhost:8080/people?q={"filter":{"and":[{"propertyName":"lastName","operator":"EQUALS","value":"Skywalker"},{"propertyName":"lastName","operator":"EQUALS","value":"Luke"}]}}'
This is an alternative approach to the one provided by the module querity-parser.
See Support for Spring MVC and REST APIs for more details.
querity-parser
Enables the parsing of Querity objects from a simple textual query language.
For example, your API calls will look like this:
curl 'http://localhost:8080/people?q=and(lastName="Skywalker",firstName="Luke")'
This is an alternative approach to the one provided by the module querity-spring-web.
See Query language for more details.
Quick start
In your Spring Boot project, add the dependency as shown in Installing and create a Spring service as follows:
import static io.github.queritylib.querity.api.Querity.*;
import static io.github.queritylib.querity.api.Operator.*;
import static io.github.queritylib.querity.api.Sort.Direction.*;
@Service
public class MyService {
@Autowired
Querity querity;
public Result<Person> getPeople() {
Query query = Querity.query()
.filter(
not(and(
filterBy("lastName", EQUALS, "Skywalker"),
filterBy("firstName", EQUALS, "Luke")
))
)
.sort(sortBy("lastName"), sortBy("birthDate", DESC))
.pagination(1, 10)
.build();
List<Person> items = querity.findAll(Person.class, query);
Long totalCount = querity.count(Person.class, query.getFilter());
return new Result<>(items, totalCount);
}
record Result<T>(List<T> items, Long totalCount) {
}
}
In the above example, the findAll method returns the first of n pages with max 10 elements of all people NOT named
Luke Skywalker, sorted by last name and then birthdate descending.
The count method returns the total filtered items count excluding pagination (the record keyword is implemented from
Java 14).
Notice the static imports to improve the readability.
Features
Use the static method Querity.query().build() to build an empty query (see the following chapters to build a more
complex query).
You can @Autowire the Querity service to run queries against the database configured in your application.
Having the Querity service, you can use the following instance methods:
Querity.findAll(entityClass, query)to run the query and retrieve the results;Querity.count(entityClass, condition)to just count the elements filtered by the condition.
Filters
Use Querity.query().filter(condition).build() to build a query with filters.
You can build filters which contains conditions, and they can be simple or nested conditions.
Conditions
Simple conditions
Use Querity.filterBy to build a simple condition with a property name, an operator and a value (if needed by the
operator, e.g. IS_NULL does not need a value).
Query query = Querity.query()
.filter(filterBy("lastName", EQUALS, "Skywalker"))
.build();
Supports nested properties with dot notation, also with one-to-many/many-to-many relationships.
E.g. address.city (one-to-one), visitedPlaces.country (one-to-many).
Operators
- EQUALS
- NOT_EQUALS
- STARTS_WITH (case-insensitive where supported*)
- ENDS_WITH (case-insensitive where supported*)
- CONTAINS (case-insensitive where supported*)
- GREATER_THAN
- GREATER_THAN_EQUALS
- LESSER_THAN
- LESSER_THAN_EQUALS
- IS_NULL
- IS_NOT_NULL
- IN
- NOT_IN
* Operators STARTS_WITH, ENDS_WITH, CONTAINS are case-insensitive only if the underlying database supports case-insensitive matching and proper configurations are applied.
E.g. JPA supports case-insensitive matching by default, while Elasticsearch requires a specific configuration (see Elasticsearch documentation and Spring Data Elasticsearch Documentation).
AND conditions
Use Querity.and and add more conditions to wrap them in a logical AND.
Query query = Querity.query()
.filter(
and(
filterBy("firstName", EQUALS, "Luke"),
filterBy("lastName", EQUALS, "Skywalker")
)
).build();
You can also nest more levels of complex conditions.
OR conditions
Use Querity.or and add more conditions to wrap them in a logical OR.
Query query = Querity.query()
.filter(
or(
filterBy("lastName", EQUALS, "Skywalker"),
filterBy("lastName", EQUALS, "Kenobi")
)
).build();
You can also nest more levels of complex conditions.
NOT conditions
Use Querity.not and specify a condition to wrap it in a logical NOT.
You can wrap simple conditions:
Query query = Querity.query()
.filter(not(filterBy("lastName", EQUALS, "Skywalker")))
.build();
or complex conditions:
Query query = Querity.query()
.filter(
not(and(
filterBy("firstName", EQUALS, "Luke"),
filterBy("lastName", EQUALS, "Skywalker")
))
).build();
Native conditions
Use Querity.filterByNative and specify a native condition to use a database-specific condition in your query.
This could be useful if you really want to add very complex query conditions that cannot be built with the Querity APIs.
Native conditions are supported only with Java API, not REST.
Example with Spring Data JPA / Jakarta Persistence:
Specification<Person> specification = (root, cq, cb) -> cb.equal(root.get("lastName"), "Skywalker");
Query query = Querity.query()
.filter(filterByNative(specification))
.build();
In the module implementing the Spring Data JPA support, the Spring Data JPA’s
Specificationclass is used.In the module implementing the Jakarta Persistence support, there is a class named
Specificationthat does the same (since the base library doesn’t provide anything similar).
Example with Spring Data MongoDB / Elasticsearch:
Criteria criteria = Criteria.where("lastName").is("Skywalker");
Query query = Querity.query()
.filter(filterByNative(criteria))
.build();
Sorting
Use Querity.query().sort(...).build() to build a query with sorting.
Use Querity.sortBy to build a sort criteria.
Query query = Querity.query()
.sort(sortBy("lastName"), sortBy("birthDate", DESC))
.build();
Use Querity.query().pagination(page, pageSize).build() to build a query with pagination.
Query query = Querity.query()
.pagination(1, 5)
.build();
Distinct results
Use Querity.query().distinct(true).build() to build a query with distinct results.
You should set this flag to true when you are filtering by some nested properties that may produce duplicate results in SQL databases.
Query query = Querity.query()
.distinct(true)
.filter(filterBy("orders.items.quantity", GREATER_THAN, 8))
.pagination(1, 5)
.build();
The distinct flag is meaningless in NoSQL databases and will be ignored.
Projections (Select)
Use Querity.advancedQuery() with selectBy to retrieve only specific fields instead of full entities.
AdvancedQuery query = Querity.advancedQuery()
.select(selectBy("firstName", "lastName", "address.city"))
.filter(filterBy("lastName", EQUALS, "Skywalker"))
.build();
List<Map<String, Object>> results = querity.findAllProjected(Person.class, query);
// Each map contains only: {firstName: "...", lastName: "...", city: "..."}
Supports nested properties with dot notation (e.g., address.city).
Query vs AdvancedQuery
Querity provides two query types:
Query- For simple entity queries. Use withfindAll()to retrieve full entities.AdvancedQuery- For projection queries withselectBy,groupBy,having. Use withfindAllProjected()to retrieveMap<String, Object>results.
// Simple entity query
Query query = Querity.query()
.filter(filterBy("lastName", EQUALS, "Skywalker"))
.build();
List<Person> entities = querity.findAll(Person.class, query);
// Projection query
AdvancedQuery advQuery = Querity.advancedQuery()
.select(selectBy("firstName", "lastName"))
.filter(filterBy("lastName", EQUALS, "Skywalker"))
.build();
List<Map<String, Object>> projections = querity.findAllProjected(Person.class, advQuery);
Native select expressions (JPA only)
For advanced use cases, JPA modules support native expressions using CriteriaBuilder:
// Select concatenated full name using native expression
SelectionSpecification<Person> fullNameSpec = AliasedSelectionSpecification.of(
(root, cb) -> cb.concat(cb.concat(root.get("firstName"), " "), root.get("lastName")),
"fullName"
);
AdvancedQuery query = Querity.advancedQuery()
.select(selectByNative(fullNameSpec))
.build();
List<Map<String, Object>> results = querity.findAllProjected(Person.class, query);
// Each map contains: {fullName: "Luke Skywalker"}
Native select expressions are only available for JPA.
Field-to-Field Comparison
Use filterByField to compare two entity fields directly:
// Find orders where quantity exceeds available stock
Query query = Querity.query()
.filter(filterByField("quantity", GREATER_THAN, field("availableStock")))
.build();
// Find employees whose salary exceeds their manager's salary
Query query = Querity.query()
.filter(filterByField("salary", GREATER_THAN, field("manager.salary")))
.build();
Use Querity.field("propertyName") to reference a field on the right-hand side of the comparison.
Supports all comparison operators: EQUALS, NOT_EQUALS, GREATER_THAN, GREATER_THAN_EQUALS, LESSER_THAN, LESSER_THAN_EQUALS.
Backend support:
- JPA: Full support
- MongoDB: Full support
- Elasticsearch: Not supported
Function Expressions
Querity supports SQL-like functions in filters, sorting, and projections. Use prop() for property references and lit() for literal values.
Using functions in filters
import static io.github.queritylib.querity.api.Querity.*;
// Filter by uppercase lastName
Query query = Querity.query()
.filter(filterBy(upper(prop("lastName")), EQUALS, "SKYWALKER"))
.build();
// Filter by string length
Query query = Querity.query()
.filter(filterBy(length(prop("firstName")), GREATER_THAN, 5))
.build();
Using functions in sorting
// Sort by string length
Query query = Querity.query()
.sort(sortBy(length(prop("lastName")), DESC))
.build();
// Sort by uppercase value
Query query = Querity.query()
.sort(sortBy(upper(prop("firstName"))))
.build();
Using functions in projections
// Select with function expressions
AdvancedQuery query = Querity.advancedQuery()
.select(selectBy(
prop("id"),
upper(prop("lastName")).as("upperLastName"),
concat(prop("firstName"), lit(" "), prop("lastName")).as("fullName"),
length(prop("email")).as("emailLength")
))
.build();
Available functions
| Category | Functions |
|---|---|
| Arithmetic | abs(), sqrt(), mod() |
| String | concat(), substring(), trim(), ltrim(), rtrim(), lower(), upper(), length(), locate() |
| Date/Time | currentDate(), currentTime(), currentTimestamp() |
| Conditional | coalesce(), nullif() |
| Aggregate | count(), sum(), avg(), min(), max() |
Function arguments
Function arguments must be either property references or literals:
prop("fieldName")- reference to an entity property (alias forproperty())lit(value)- literal value (String, Number, or Boolean)
// Combine functions
coalesce(prop("nickname"), lit("Anonymous"))
mod(prop("quantity"), lit(10))
concat(prop("firstName"), lit(" - "), prop("lastName"))
// Nested functions
upper(trim(prop("name"))) // UPPER(TRIM(name))
length(lower(prop("email"))) // LENGTH(LOWER(email))
// Nested properties (e.g., address.city)
upper(prop("address.city"))
coalesce(prop("contact.email"), prop("contact.phone"), lit("N/A"))
Backend support for functions
- JPA: Full support for all functions in filters, sorting, and projections
- MongoDB: Functions supported in filters only (via
$expr). Using functions in sort or select throwsUnsupportedOperationException- Elasticsearch: Functions are not supported. Using functions throws
UnsupportedOperationException
Function support by implementation:
| Function | JPA | MongoDB | Elasticsearch |
|---|---|---|---|
abs(), sqrt(), mod() |
✓ Filters, Sort, Select | ✓ Filters only | ✗ |
concat(), substring(), trim(), ltrim(), rtrim() |
✓ Filters, Sort, Select | ✓ Filters only | ✗ |
lower(), upper(), length(), locate() |
✓ Filters, Sort, Select | ✓ Filters only | ✗ |
currentDate(), currentTime(), currentTimestamp() |
✓ Filters, Sort, Select | ✓ Filters only | ✗ |
coalesce(), nullif() |
✓ Filters, Sort, Select | ✓ Filters only | ✗ |
count(), sum(), avg(), min(), max() |
✓ Filters, Sort, Select | ✓ Filters only | ✗ |
GROUP BY and HAVING
Querity supports GROUP BY and HAVING clauses for aggregate queries. Use advancedQuery() with groupBy to group results and having to filter groups.
Basic GROUP BY
import static io.github.queritylib.querity.api.Querity.*;
// Group by single property
AdvancedQuery query = Querity.advancedQuery()
.select(selectBy(
prop("category"),
count(prop("id")).as("itemCount"),
sum(prop("amount")).as("totalAmount")
))
.groupBy("category")
.build();
List<Map<String, Object>> results = querity.findAllProjected(Order.class, query);
// Returns: [{category: "Electronics", itemCount: 42, totalAmount: 15000}, ...]
Group by multiple properties
AdvancedQuery query = Querity.advancedQuery()
.select(selectBy(
prop("category"),
prop("region"),
avg(prop("price")).as("avgPrice")
))
.groupBy("category", "region")
.build();
Group by with HAVING clause
AdvancedQuery query = Querity.advancedQuery()
.select(selectBy(
prop("category"),
sum(prop("amount")).as("total")
))
.groupBy("category")
.having(filterBy(sum(prop("amount")), GREATER_THAN, 1000))
.build();
// Returns only categories with total amount > 1000
Group by with function expressions
// Group by function result (e.g., group by uppercase category)
AdvancedQuery query = Querity.advancedQuery()
.select(selectBy(
upper(prop("category")).as("upperCategory"),
count(prop("id")).as("orderCount")
))
.groupBy(upper(prop("category")))
.build();
Backend support:
- JPA: Full support for GROUP BY and HAVING
- MongoDB: Not yet supported
- Elasticsearch: Not supported
Native Sort Expressions
Use sortByNative to sort by database-specific expressions.
JPA native sort
// Sort by length of lastName using CriteriaBuilder
OrderSpecification<Person> orderSpec = (root, cb) -> cb.asc(cb.length(root.get("lastName")));
Query query = Querity.query()
.sort(sortByNative(orderSpec))
.build();
List<Person> results = querity.findAll(Person.class, query);
MongoDB native sort
// Sort using Spring Data MongoDB's Order
org.springframework.data.domain.Sort.Order order = Sort.Order.asc("lastName");
Query query = Querity.query()
.sort(sortByNative(order))
.build();
Query Customizers (JPA)
You can customize JPA queries with specific hints and optimizations using the .customize() method. This is useful for performance tuning.
Fetch Join (Eager Loading)
Solve N+1 query problems by fetching associated entities eagerly:
import io.github.queritylib.querity.jpa.JPAHints;
// Fetch associated entities eagerly to avoid N+1 queries
Query query = Querity.query()
.filter(filterBy("status", EQUALS, "ACTIVE"))
.customize(JPAHints.fetchJoin("orders", "orders.items")) // Supports nested paths
.build();
List<Person> results = querity.findAll(Person.class, query);
Named Entity Graphs
Use JPA entity graphs for fetch optimization:
Query query = Querity.query()
.customize(JPAHints.namedEntityGraph("Person.withOrders"))
.build();
Other Optimizations
Query query = Querity.query()
.customize(
JPAHints.batchSize(50), // Optimize JDBC fetch size
JPAHints.timeout(5000), // Query timeout in ms
JPAHints.cacheable(true) // Enable L2 Query Cache
)
.build();
Note: Customizers are backend-specific.
JPAHintswill only be applied when using the JPA module and are safely ignored by other modules like MongoDB.
Modify an existing Query
Query objects are immutable, so you can’t modify them directly (there are no “setters”).
You can build a new query by copying the existing one and changing the parts you need.
Use Query.toBuilder() to copy an existing Query into a new QueryBuilder, then you can make changes before calling build().
Query query = originalQuery.toBuilder()
.sort(sortBy("lastName"))
.build();
Support for Spring MVC and REST APIs
Querity objects need some configuration to be correctly deserialized when they are received by a
Spring @RestController as a JSON payload.
This includes:
- registering property editors in Spring MVC to recognize the
QuerityandConditionobjects as valid request parameters, - configuring the Jackson module
QuerityModuleto correctly deserialize the Querity objects from JSON, - configuring the aspect
QuerityPreprocessorAspectto use the preprocessors in the Spring controllers (see Preprocessors below).
These configurations are automatically done by importing the querity-spring-web module (see Installing).
You can disable the autoconfiguration of the Spring Boot starter by adding the following property to your
application.properties:
querity.web.autoconfigure.enabled=falseThen you will have to apply the needed configurations manually.
After that, you’ll be able to use a Query or Condition as a controller parameter and build REST APIs like this:
import io.github.queritylib.querity.api.Query;
@RestController
public class MyRestController {
@Autowired
MyService service;
@GetMapping(value = "/people", produces = MediaType.APPLICATION_JSON_VALUE)
Result<Person> getPeople(@RequestParam(required = false) Query q) {
return service.getPeople(q);
}
}
Then the above REST API could be invoked like this:
curl 'http://localhost:8080/people?q={"filter":{"and":[{"propertyName":"lastName","operator":"EQUALS","value":"Skywalker"},{"propertyName":"lastName","operator":"EQUALS","value":"Luke"}]}}'
Query language
The querity-parser module provides a simple query language to build a Query object,
useful when you need the user to write and understand the query.
It is an alternative approach to the one provided by the module querity-spring-web, which parses JSON.
To enable the query language, import the querity-parser module (see Installing).
The following snippet rewrites the previous example using the support for the query language:
import io.github.queritylib.querity.api.Query;
import io.github.queritylib.querity.parser.QuerityParser;
@RestController
public class MyRestController {
@Autowired
MyService service;
@GetMapping(value = "/people", produces = MediaType.APPLICATION_JSON_VALUE)
Result<Person> getPeople(@RequestParam(required = false) String q) {
Query query = QuerityParser.parseQuery(q);
return service.getPeople(query);
}
}
Then the above REST API could be invoked like this:
curl 'http://localhost:8080/people?q=and(lastName="Skywalker",firstName="Luke")'
Much simpler than JSON, isn’t it?
Query language syntax
The query language supports the following grammar (ANTLR v4 format):
DISTINCT : 'distinct';
WHERE : 'where';
AND : 'and';
OR : 'or';
NOT : 'not';
SORT : 'sort by';
ASC : 'asc';
DESC : 'desc';
PAGINATION : 'page';
SELECT : 'select';
GROUP_BY : 'group by';
HAVING : 'having';
AS : 'as';
NEQ : '!=';
LTE : '<=';
GTE : '>=';
EQ : '=';
LT : '<';
GT : '>';
STARTS_WITH : 'starts with';
ENDS_WITH : 'ends with';
CONTAINS : 'contains';
IS_NULL : 'is null';
IS_NOT_NULL : 'is not null';
IN : 'in';
NOT_IN : 'not in';
LPAREN : '(';
RPAREN : ')';
COMMA : ',';
INT_VALUE : [0-9]+;
DECIMAL_VALUE : [0-9]+'.'[0-9]+;
BOOLEAN_VALUE : 'true' | 'false';
PROPERTY : [a-zA-Z_][a-zA-Z0-9_.]*;
STRING_VALUE : '"' (~["\\] | '\\' .)* '"';
FUNCTION_NAME : 'UPPER' | 'LOWER' | 'LENGTH' | 'TRIM' | 'LTRIM' | 'RTRIM' |
'ABS' | 'SQRT' | 'MOD' | 'CONCAT' | 'SUBSTRING' | 'LOCATE' |
'COALESCE' | 'NULLIF' | 'CURRENT_DATE' | 'CURRENT_TIME' |
'CURRENT_TIMESTAMP' | 'COUNT' | 'SUM' | 'AVG' | 'MIN' | 'MAX';
query : DISTINCT? (WHERE? condition)? (SORT sortFields)? (PAGINATION paginationParams)? ;
advancedQuery : (SELECT selectFields)? (WHERE? condition)? (GROUP_BY groupByFields)? (HAVING havingCondition)? (SORT sortFields)? (PAGINATION paginationParams)? ;
condition : simpleCondition | conditionWrapper | notCondition;
operator : NEQ | LTE | GTE | EQ | LT | GT | STARTS_WITH | ENDS_WITH | CONTAINS | IS_NULL | IS_NOT_NULL | IN | NOT_IN ;
conditionWrapper : (AND | OR) LPAREN condition (COMMA condition)* RPAREN ;
notCondition : NOT LPAREN condition RPAREN ;
simpleValue : INT_VALUE | DECIMAL_VALUE | BOOLEAN_VALUE | STRING_VALUE;
arrayValue : LPAREN simpleValue (COMMA simpleValue)* RPAREN ;
propertyOrFunction : PROPERTY | functionCall ;
functionCall : FUNCTION_NAME LPAREN (functionArg (COMMA functionArg)*)? RPAREN ;
functionArg : PROPERTY | simpleValue | functionCall ;
simpleCondition : propertyOrFunction operator (simpleValue | arrayValue)? ;
direction : ASC | DESC ;
sortField : propertyOrFunction (direction)? ;
sortFields : sortField (COMMA sortField)* ;
selectField : propertyOrFunction (AS PROPERTY)? ;
selectFields : selectField (COMMA selectField)* ;
groupByFields : propertyOrFunction (COMMA propertyOrFunction)* ;
havingCondition : condition ;
paginationParams : INT_VALUE COMMA INT_VALUE ;
Some examples of valid queries:
lastName="Skywalker"
lastName!="Skywalker"
lastName starts with "Sky"
lastName ends with "walker"
lastName contains "wal"
and(firstName="Luke", lastName="Skywalker")
age>30
age<30
height>=1.80
height<=1.80
and(lastName="Skywalker", age>30)
and(or(firstName="Luke", firstName="Anakin"), lastName="Skywalker") sort by age desc
and(not(firstName="Luke"), lastName="Skywalker")
lastName="Skywalker" page 2,10
lastName is null
lastName is not null
lastName in ("Skywalker", "Solo")
lastName not in ("Skywalker", "Solo")
deleted=false
address.city="Rome"
distinct and(orders.totalPrice>1000,currency="EUR")
sort by lastName asc, age desc page 1,10
Function expressions in query language:
UPPER(lastName)="SKYWALKER"
LENGTH(firstName)>5
LOWER(email) starts with "luke"
and(UPPER(lastName)="SKYWALKER", LENGTH(firstName)>3)
sort by LENGTH(lastName) desc
COALESCE(nickname, firstName)="Luke"
Optional WHERE keyword:
The WHERE keyword is optional and can be used for readability:
where lastName="Skywalker"
where and(firstName="Luke", lastName="Skywalker") sort by age
distinct where status="ACTIVE" page 1,10
SELECT, GROUP BY, and HAVING (AdvancedQuery):
Use QuerityParser.parseAdvancedQuery() for queries with projections or grouping:
select firstName, lastName, address.city
select id, UPPER(lastName) as upperName
select category, COUNT(id) as itemCount group by category
select category, SUM(amount) as total group by category having SUM(amount)>1000
select region, category, AVG(price) as avgPrice group by region, category sort by avgPrice desc
Notice that string values must always be enclosed in double quotes.
Support for DTO layer
You may not want to expose database entities to the clients of your REST APIs.
If you implemented a DTO layer, with DTO objects mapping properties from your entities, the queries which would come to your REST APIs will have the DTO property names, not the entity ones. This is a problem, because some queries would not work, looking for non-existing properties on the entities.
Note: it would work without any help only if properties in the DTO have the same name and structure of the properties in the entity
Querity has a “preprocessing layer” which you can use to map the DTO property names in your Query to the entity property names.
Preprocessors
Use the @WithPreprocessor("beanName") annotation to annotate Query or Condition parameters in your Spring RestControllers.
The beanName argument must refer to an existing Spring bean which implements QueryPreprocessor.
When entering your controller method, the Query or Condition object would already be preprocessed.
PropertyNameMappingPreprocessor
PropertyNameMappingPreprocessor is a QueryProcessor abstraction to convert property names.
Querity already has a simple mapper, SimplePropertyNameMapper, which simply does a property name conversion by looking into a Map you must provide.
To use PropertyNameMappingPreprocessor with SimplePropertyNameMapper, instantiate a bean into your Spring configuration:
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
@Bean
public QueryPreprocessor preprocessor1() {
return new PropertyNameMappingPreprocessor(
SimplePropertyNameMapper.builder()
.recursive(true) // default is true
.mapping("prop1", "prop2") // customize your mappings here
.build());
}
}
and use it to annotate the parameter in your RestController:
@RestController
public class MyRestController {
@Autowired
MyService service;
@GetMapping(value = "/people", produces = MediaType.APPLICATION_JSON_VALUE)
Result<Person> getPeople(@RequestParam(required = false) @WithPreprocessor("preprocessor1") Query q) {
return service.getPeople(q);
}
}
The mappings in SimplePropertyNameMapper are resolved recursively by default.
So if you mapped prop1 to prop2, then all the fields nested in prop1 will be automatically mapped to equal-named fields under prop2.
You can switch off the recursive mapping by setting the recursive flag to false in the builder.
React Components
Querity provides a library of React components designed to simplify the creation of user interfaces for Querity-based REST APIs.
These components are available as the @queritylib/react package on npm.
- Live Demo: See these components in action in the querity-demo project.
- Documentation: Explore the documentation to start integrating Querity React Components into your React application.
Get started today and build powerful, dynamic UIs with ease!