Skip to content

Getting started without Spring

Everything in the library works with a plain EntityManager. Spring only adds wiring.

1. Add the dependencies

Import the BOM, then add the modules you need. Replace 0.6.0 with the release you use.

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>io.github.rey5137</groupId>
            <artifactId>model-query-bom</artifactId>
            <version>0.6.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>io.github.rey5137</groupId>
        <artifactId>model-query-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>io.github.rey5137</groupId>
        <artifactId>model-query-annotations</artifactId>
    </dependency>
</dependencies>

Register the processor so the Q classes are generated at compile time:

<annotationProcessorPaths>
    <path>
        <groupId>io.github.rey5137</groupId>
        <artifactId>model-query-processor</artifactId>
        <version>0.6.0</version>
    </path>
</annotationProcessorPaths>

Add model-query-hibernate as well when you run on Hibernate: it detects the database from the dialect without a connection, and lets the library render NULLS FIRST/LAST and count grouped queries natively.

2. Describe a result as a model

A model is a class or a record annotated with @QueryModel, naming the JPA entity it reads from.

@QueryModel(root = OrderEntity.class)
@FilterColumn(name = "ITEM_SKU", path = "items.sku", joinType = JoinKind.LEFT)
public class OrderView {

    @PrimaryKey
    private Long id;
    private String status;
    private BigDecimal total;
    // Empty for a walk-in sale, which has no customer.
    @Join
    private Optional<CustomerView> customer = Optional.empty();

    // getters and setters
}

The processor generates QOrderView next to it. See Models and QModels for what it generates.

3. Create an executor and query

var executor = ModelQueryExecutor.create(em, OrderEntity.class, ModelQueryConfig.defaults());

// A filtered page of order views, each with its customer, if any.
var paid = QOrderView.query()
        .select(QOrderView.ALL.with(QOrderView.CUSTOMER))
        .where(f -> f.eq(QOrderView.STATUS, "PAID"))
        .orderBy(QOrderView.ID.asc())
        .build();

Slice<OrderView> page = executor.page(paid, PageSpec.of(0, 20), CountMode.COUNT);

A ModelQuery is immutable, so you can keep it in a static final field and share it. The executor is created once per EntityManager and entity type.

ModelQueryConfig.defaults() is a good start. Each tuning knob is a method on the config, for example exportPageSize(int), streamFetchSize(int) or queryTimeout(Duration); the Spring Data and the starter page lists every setting next to its property name.

4. A full example

The repository ships a runnable sample, samples/plain-jpa, with a small shop on in-memory H2: a filtered page, an exists filter on a collection, and a grouped summary, all read through generated Q classes. Run it from the repository root:

./mvnw -q -pl samples/plain-jpa -am package -DskipTests -Prun

Next: Models and QModels.