Query Operations
Qubit provides a fluent API for building type-safe queries with filtering, sorting, pagination, and projections.
Filtering
Use where() to filter entities. Multiple calls combine with AND:
// Simple filter
List<Person> adults = personRepository
.where((Person p) -> p.age >= 18)
.toList();
// Multiple conditions
List<Person> activeAdults = personRepository
.where((Person p) -> p.age >= 18)
.where((Person p) -> p.active)
.toList();
// Combined in single lambda
List<Person> results = personRepository
.where((Person p) -> p.age >= 18 && p.active)
.toList();
Projection
Use select() to project entities to different types:
// Project to single field
List<String> names = personRepository
.select((Person p) -> p.firstName)
.toList();
// Project to DTO
List<PersonDTO> dtos = personRepository
.select((Person p) -> new PersonDTO(p.firstName, p.lastName))
.toList();
Sorting
Use sortedBy() for ascending order and sortedDescendingBy() for descending:
// Ascending
List<Person> byName = personRepository
.sortedBy((Person p) -> p.lastName)
.toList();
// Descending
List<Person> byAgeDesc = personRepository
.sortedDescendingBy((Person p) -> p.age)
.toList();
// Multiple sort keys: primary by lastName, secondary by firstName
List<Person> sorted = personRepository
.sortedBy((Person p) -> p.lastName)
.thenSortedBy((Person p) -> p.firstName)
.toList();
Pagination
Use skip() and limit() for pagination:
// Skip first 10, take next 20
List<Person> page = personRepository
.sortedBy((Person p) -> p.id)
.skip(10)
.limit(20)
.toList();
Distinct
Use distinct() to eliminate duplicates:
List<String> departments = personRepository
.select((Person p) -> p.department.name)
.distinct()
.toList();
Terminal Operations
Terminal operations execute the query and return results:
| Method | Description |
|---|---|
|
Returns all results as a list |
|
Returns exactly one result; throws if none or multiple |
|
Returns an Optional for the first result |
|
Returns the count of matching entities |
|
Returns true if any results match |
Aggregations
Compute aggregate values across entities. Use sumInteger() for Integer fields, sumLong() for Long fields, and sumDouble() for Double fields:
// Minimum
Integer minAge = personRepository
.min((Person p) -> p.age)
.getSingleResult();
// Maximum
Double maxSalary = personRepository
.max((Person p) -> p.salary)
.getSingleResult();
// Average
Double avgSalary = personRepository
.avg((Person p) -> p.salary)
.getSingleResult();
// Sum
Long totalAge = personRepository
.sumInteger((Person p) -> p.age)
.getSingleResult();
IN Clause
Use contains() for IN queries:
List<String> names = List.of("John", "Jane", "Alice");
List<Person> matching = personRepository
.where((Person p) -> names.contains(p.firstName))
.toList();
Supported Expressions
| Category | Examples |
|---|---|
Comparison |
|
Logical |
|
Arithmetic |
|
|
|
String |
|
Null |
|
Temporal |
|
BigDecimal |
|
Ternary |
|
Inheritance (TREAT) |
|
Constant Folding |
Static utility methods with constant or captured variable arguments are automatically evaluated: |
Query Optimizations
Qubit automatically detects common patterns and generates optimized JPA Criteria API calls:
BETWEEN: Range checks like p.age >= 18 && p.age ⇐ 65 generate cb.between() instead of separate cb.greaterThanOrEqualTo() and cb.lessThanOrEqualTo() calls. All operand orderings are recognized (low <= field, field >= low, etc.).
NULLIF: Ternary expressions like p.status.equals("UNKNOWN") ? null : p.status generate cb.nullif() instead of a CASE WHEN expression. Both equals and != forms are detected.
These optimizations are transparent — write natural Java and Qubit applies the best JPA mapping automatically.