Querying read models
Read models are retrieved through a repository interface that Spring DDD generates at startup. Declaring the interface is all that is required — the projection store starter provides the underlying implementation automatically.
JSON read models (JsonProjectionRepository)
For a @JsonProjection read model, declare a subinterface of JsonProjectionRepository and inject
it where needed:
interface AccountSummaryRepository : JsonProjectionRepository<AccountSummary, AccountId>
Spring DDD registers the implementation at startup; no @Bean declaration or factory is needed.
Finding by identity
val summary: AccountSummary? = repository.findByIdOrNull(accountId)
findByIdOrNull returns the read model with the given id, or null if none exists.
For a GLOBAL-scope projection the read-model key is the projection’s name attribute, so pass
that string as the id.
Paged retrieval
val page: Page<AccountSummary> = repository.findAll(PageRequest.of(0, 20))
findAll(pageable: Pageable): Page<T> returns a page of read models.
Without a sort the results are ordered by read_model_id ASC.
When a sort is present, every sort property must be a declared @QueryField column — sorting on
the JSON blob is not supported.
Querying by @QueryField columns
On the JDBC store you can only query a @JsonProjection read model by fields that have been
promoted to indexed query columns with @QueryField; derived finders and sort properties never
reach inside the JSON blob.
For how to declare these columns — the @QueryField attributes, typed (NUMBER / TEMPORAL /
BOOLEAN) columns, precision/scale, the dedicated-table rule, and the per-dialect DDL — see
JSON projections.
Derived finders that query against those columns are shown in Derived finders below.
In-memory read models (InMemoryProjectionRepository)
@InMemoryProjection read models expose the same findByIdOrNull and findAll entry points, plus
additional query capabilities available only with in-memory storage.
Declare a subinterface to gain access to derived finders:
interface AccountSummaryRepository : InMemoryProjectionRepository<AccountSummary, AccountId>
Predicate queries (in-memory only)
Because read models are heap-resident, the in-memory repository supports arbitrary predicate-based
queries.
This capability is not available on JsonProjectionRepository or JPA-backed repositories.
// Find the single open account for a given owner — throws if more than one matches
val summary: AccountSummary? = repository.findOne(Predicate { it.owner == ownerId })
// Find all overdrawn accounts, sorted by balance ascending
val overdrawn: List<AccountSummary> = repository.findAll(
Predicate { it.balance < BigDecimal.ZERO },
Sort.by("balance")
)
// Paginate results matching a predicate
val page: Page<AccountSummary> = repository.findAll(
Predicate { it.status == AccountStatus.ACTIVE },
PageRequest.of(0, 20, Sort.by("balance").descending())
)
Full predicate API on InMemoryProjectionRepository<T, ID>:
fun findOne(predicate: Predicate<T>): T?
fun findAll(predicate: Predicate<T>): List<T>
fun findAll(predicate: Predicate<T>, sort: Sort): List<T>
fun findAll(predicate: Predicate<T>, comparator: Comparator<T>): List<T>
fun findAll(predicate: Predicate<T>, pageable: Pageable): Page<T>
findOne throws IllegalStateException if more than one read model matches; it returns null
if none match.
All predicate overloads perform a full in-memory scan.
Derived finders
Both JsonProjectionRepository and InMemoryProjectionRepository support Spring Data–style
derived finder methods declared on a subinterface.
The naming convention follows the same
Spring Data derived-query grammar
(e.g. findBy<Property>, findAllBy<Property>Between, findBy<A>And<B>).
Derived finders on JsonProjectionRepository
For the JDBC store, derived finders are restricted to properties annotated with @QueryField.
Each query translates to a SQL predicate against the dedicated query column.
interface AccountSummaryRepository : JsonProjectionRepository<AccountSummary, AccountId> {
// @QueryField(unique = true) property → single-result finder
fun findByAccountNumber(number: String): AccountSummary?
// collection result
fun findAllByOwner(owner: OwnerId): List<AccountSummary>
// paged result
fun findAllByOwner(owner: OwnerId, pageable: Pageable): Page<AccountSummary>
// range predicate on a @QueryField(type = NUMBER) property
fun findAllByBalanceGreaterThan(threshold: BigDecimal): List<AccountSummary>
}
Spring DDD validates all declared derived finders at startup.
A finder that references a property without @QueryField, or that uses a return type
incompatible with the column’s unique setting, fails fast with a descriptive error.
Derived finders on InMemoryProjectionRepository
For in-memory read models, derived finders run as a reflective full scan over the heap-resident
models.
No @QueryField annotation is required — any readable property can appear in a finder name.
interface AccountSummaryRepository : InMemoryProjectionRepository<AccountSummary, AccountId> {
fun findByOwner(owner: OwnerId): AccountSummary?
fun findAllByStatus(status: AccountStatus): List<AccountSummary>
}
Bulk loading
Both repositories support retrieving multiple read models in a single call.
JSON store
val page: Page<AccountSummary> = repository.findAll(PageRequest.of(0, 100))
findAll(pageable: Pageable): Page<T> is available on JsonProjectionRepository.
Results are ordered by read_model_id ASC when no sort is specified;
sort properties must map to declared @QueryField columns.
In-memory store
InMemoryProjectionRepository additionally provides a sort-only overload that returns all matching
read models without a page boundary:
val all: List<AccountSummary> = repository.findAll(Sort.by("balance").descending())
findAll(sort: Sort): List<T> returns every heap-resident read model, ordered reflectively on
the given property.
This overload is in-memory only and is well-suited for small-to-medium read-model populations
where paging is not required.