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

toList()

Returns all results as a list

getSingleResult()

Returns exactly one result; throws if none or multiple

findFirst()

Returns an Optional for the first result

count()

Returns the count of matching entities

exists()

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

+, -, *, /, %

Math

Math.abs(), Math.sqrt(), Math.ceil(), Math.floor(), Math.exp(), Math.log(), Math.pow(), Math.round(), Integer.signum(), QubitMath.round()

String

startsWith(), endsWith(), contains(), toLowerCase(), toUpperCase(), trim(), length(), substring(), indexOf(), replace(), Qubit.like(), Qubit.notLike(), Qubit.left(), Qubit.right()

Null

== null, != null

Temporal

isAfter(), isBefore(), isEqual(), getYear(), getMonth(), getDayOfMonth(), getHour(), getMinute(), getSecond() for LocalDate, LocalDateTime, LocalTime

BigDecimal

compareTo()

Ternary

condition ? trueExpr : falseExpr in projections

Inheritance (TREAT)

a instanceof Dog d && d.breed, ((Dog) a).breed — access subclass fields via pattern matching or explicit cast

Constant Folding

Static utility methods with constant or captured variable arguments are automatically evaluated: MyUtils.toUpper("hello") at build time, MyUtils.toUpper(searchTerm) at query execution time

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.