Architecture¶
How the modules fit together, and what happens between a query definition and the models it returns.
Modules¶
Dependencies flow one way. core knows nothing about the executor, and nothing below the starter knows about Spring
Boot, so every feature works with a plain EntityManager.
flowchart BT
annotations[model-query-annotations]
core[model-query-core]
jpa[model-query-jpa]
hibernate[model-query-hibernate]
springdata[model-query-spring-data]
starter[model-query-spring-boot-starter]
processor[model-query-processor]
test[model-query-test]
core --> annotations
jpa --> core
hibernate --> jpa
springdata --> jpa
starter --> hibernate
starter --> springdata
processor --> annotations
test --> core
| Layer | Modules | Role |
|---|---|---|
| Compile time | annotations, processor |
You annotate a query, update or insert model; the processor generates its Q class |
| Definition | core |
Immutable definitions: queries (columns, joins, filters, select sets, fetch plans, enrichers) and writes (ModelUpdate, ModelDelete, ModelInsert, ModelPersist) |
| Execution | jpa, hibernate |
The executor turns a definition into JPA criteria, pages, streams, exports, bulk writes and inserts; hibernate adds the insert support |
| Integration | spring-data, spring-boot-starter |
ModelQueryRepository and auto-configuration; no behaviour of their own |
| Testing | test |
Assertions over a recorded ModelQuery, without a database |
From a model to a result¶
flowchart TB
subgraph compile[Compile time]
model["@QueryModel class or record"] --> proc[Annotation processor] --> q["Generated Q class<br/>(columns, joins, column sets)"]
end
subgraph define[Definition, model-query-core]
mq["ModelQuery<br/>Filters, SelectSet, order, FetchPlan"]
end
subgraph run[Execution, model-query-jpa]
exec[ModelQueryExecutor] --> jc["JoinContext<br/>(per query)"] --> criteria[JPA criteria query] --> em[EntityManager]
vendor[VendorProfile] -.-> criteria
end
subgraph map[Mapping]
rows[Result rows] --> models[Models] --> plan["FetchPlan children<br/>and enrichers"]
end
q --> mq
mq --> exec
repo["ModelQueryRepository<br/>(Spring Data)"] --> exec
em <--> db[(Database)]
em --> rows
- Compile time. The processor reads each
@QueryModeland generates aQclass with one typed constant per column, join and column set. A column's Java type is checked against the entity attribute and the model field. - Definition. A
ModelQuerycombinesQconstants with aFilterstree, a select set and an order. Definitions are immutable and thread-safe, so they can bestatic finaland shared. - Execution. The
ModelQueryExecutor, called directly or through aModelQueryRepository, resolves joins in a per-queryJoinContextand builds a JPA criteria query that selects only the declared columns. Values are always bind parameters. - Vendor behaviour. Every database difference, such as null ordering, streaming or bulk-write strategy, sits
behind a
VendorProfile. H2, PostgreSQL and MySQL profiles are built in; see vendor notes. - Mapping. Each result row becomes a plain model; nothing returned is a managed entity, and queries never write. A fetch plan then loads child collections and runs enrichers once per page, not once per row; see fetch plans.
Paging and export¶
The same definition runs as list, page, count, stream or export. Offset, keyset and primary-key-first
paging are strategies inside the executor. An export visits every row exactly once, with memory bounded by one page;
see paging and export.
Writes¶
Writes are definitions too, and run through the same executor and repository. None of them loads an entity except
persist, which writes one.
flowchart TB
subgraph compile[Compile time]
um["@UpdateModel / @InsertModel"] --> proc[Annotation processor] --> q["Generated Q class<br/>(change sets, insert and persist builders)"]
end
subgraph define[Definition, model-query-core]
upd["ModelUpdate / ModelDelete"]
ins["ModelInsert<br/>(insert-select, ValuesInsert)"]
per[ModelPersist]
end
subgraph run[Execution, model-query-jpa]
exec[ModelQueryExecutor]
crit["CriteriaUpdate / CriteriaDelete"]
spi["ProviderSupport.inserts()<br/>InsertSupport"]
jpa["EntityManager.persist<br/>flush, detach"]
vendor["VendorProfile<br/>(VALUES rows, conflict target)"] -.-> spi
end
hib["HibernateProviderSupport<br/>(model-query-hibernate)"] -.-> spi
q --> upd & ins & per
upd --> exec --> crit
ins --> exec --> spi
per --> exec --> jpa
repo["ModelQueryRepository<br/>(opens a transaction if none)"] --> exec
crit & spi & jpa --> db[(Database)]
- Update and delete become JPA criteria bulk statements, optionally chunked key-first; see bulk writes.
- Insert-select and insert-values go through the provider's
InsertSupport, which Hibernate supplies. TheVendorProfilesets theVALUESrow and bind limits and says whether a conflict target is honoured. Without insert support a bulk insert fails withMQ4009; see inserts. persistinstantiates the entity, persists and flushes it through theEntityManager, and detaches it, so lifecycle callbacks run and anIDENTITYkey comes back on any provider.