blob: cd39f3f7c8d09750a8f6aed2a69683b749ee1930 [file] [view]
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
# Query API reference
How to fetch and filter persistent objects with Cayenne 5.0.
## Three query mechanisms
| API | When to use |
|---|---|
| `ObjectSelect` | Default for selecting Java entities. Fluent, type-safe. |
| `SQLSelect` / `SQLExec` | Raw SQL with Cayenne parameter binding. |
| Named queries (XML) | When the same query is reused many places; lives in the DataMap. |
## ObjectSelect — primary fluent query API
`org.apache.cayenne.query.ObjectSelect`. Factory methods:
```java
import org.apache.cayenne.query.ObjectSelect;
// Fetch all
List<Artist> all = ObjectSelect.query(Artist.class).select(ctx);
// Single by criteria
Artist picasso = ObjectSelect.query(Artist.class)
.where(Artist.ARTIST_NAME.eq("Picasso"))
.selectOne(ctx);
// First matching, null if none
Artist maybe = ObjectSelect.query(Artist.class)
.where(Artist.ARTIST_NAME.eq("Picasso"))
.selectFirst(ctx);
// Iterate large result without loading all
try (ResultIterator<Artist> it = ObjectSelect.query(Artist.class).iterator(ctx)) {
while (it.hasNextRow()) {
Artist a = it.nextRow();
...
}
}
```
### Filtering with Property constants
cgen generates `Property<T>` constants on each entity superclass: `Artist.ARTIST_NAME`, `Artist.DATE_OF_BIRTH`, `Artist.PAINTINGS`. These are the recommended way to express filters because they are type-checked.
```java
ObjectSelect.query(Artist.class)
.where(Artist.ARTIST_NAME.likeIgnoreCase("p%"))
.and(Artist.DATE_OF_BIRTH.between(d1, d2))
.or(Artist.ARTIST_NAME.eq("Anonymous"))
.select(ctx);
```
Common predicate methods on `Property<T>`:
- `.eq(value)`, `.ne(value)`
- `.lt(value)`, `.lte(value)`, `.gt(value)`, `.gte(value)`
- `.like(pattern)`, `.likeIgnoreCase(pattern)`, `.contains(s)`, `.startsWith(s)`, `.endsWith(s)`
- `.in(values...)`, `.nin(values...)`
- `.isNull()`, `.isNotNull()`
- `.between(min, max)`
### Ordering
```java
import org.apache.cayenne.query.Ordering;
import org.apache.cayenne.query.SortOrder;
ObjectSelect.query(Artist.class)
.orderBy(Artist.ARTIST_NAME.asc())
.orderBy(Artist.DATE_OF_BIRTH.desc())
.select(ctx);
```
### Pagination
```java
ObjectSelect.query(Artist.class)
.pageSize(50) // server-side pagination
.limit(1000)
.offset(0)
.select(ctx);
```
`pageSize` enables on-demand loading: the result list contains all PKs but only the requested page's full objects are materialized when accessed.
### Prefetching (avoiding N+1)
```java
// Joint: same query, single SQL
ObjectSelect.query(Artist.class)
.prefetch(Artist.PAINTINGS.joint())
.select(ctx);
// Disjoint: separate query per relationship
ObjectSelect.query(Artist.class)
.prefetch(Artist.PAINTINGS.disjoint())
.select(ctx);
// Disjoint-by-id: separate query keyed by source PKs
ObjectSelect.query(Artist.class)
.prefetch(Artist.PAINTINGS.disjointById())
.select(ctx);
```
Rule of thumb: `joint()` for to-one, `disjoint()` for to-many that fans out modestly, `disjointById()` for large to-many.
### Caching
```java
ObjectSelect.query(Artist.class)
.localCache() // per-context cache
.select(ctx);
ObjectSelect.query(Artist.class)
.sharedCache("artistGroup") // cross-context, named cache group
.select(ctx);
```
Use `sharedCache` for reference data. Invalidate by group via `cayenne-cache-invalidation` if installed.
### Aggregates and column-only queries
```java
import org.apache.cayenne.query.ColumnSelect;
import org.apache.cayenne.exp.property.PropertyFactory;
// Single column
List<String> names = ObjectSelect.columnQuery(Artist.class, Artist.ARTIST_NAME).select(ctx);
// Multiple columns
List<Object[]> rows = ObjectSelect.columnQuery(Artist.class,
Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH).select(ctx);
// Aggregate
long count = ObjectSelect.query(Artist.class).selectCount(ctx);
```
### Selecting by primary key
```java
import org.apache.cayenne.query.SelectById;
Artist a = SelectById.query(Artist.class, 42).selectOne(ctx);
```
## SQLSelect / SQLExec — raw SQL
```java
import org.apache.cayenne.query.SQLSelect;
import org.apache.cayenne.query.SQLExec;
// Select into entities
List<Artist> artists = SQLSelect.query(Artist.class,
"SELECT * FROM ARTIST WHERE ARTIST_NAME LIKE #bind($name)")
.params(Map.of("name", "P%"))
.select(ctx);
// Select into raw rows
List<Object[]> rows = SQLSelect.scalarQuery(Object[].class,
"SELECT ARTIST_ID, ARTIST_NAME FROM ARTIST")
.select(ctx);
// Execute (insert/update/delete)
int affected = SQLExec.query(
"UPDATE ARTIST SET ARTIST_NAME = UPPER(ARTIST_NAME)")
.update(ctx);
```
Parameter binding uses Velocity-like syntax: `#bind($name)`, `#bindEqual($name)`, `#bindNotEqual($name)`, `#chain('AND' 'condition' "$param")`. Never concatenate user input into SQL strings — use bindings.
## Expression — programmatic predicates
`Expression` is Cayenne's predicate AST. Most queries don't construct one directly — they let `Property` constants do it.
### Preferred: build expressions from `Property` constants
The `Property<T>` constants on each entity superclass (`Artist.ARTIST_NAME`, `Artist.DATE_OF_BIRTH`, `Artist.PAINTINGS`, etc., declared on the generated `_Artist` superclass and inherited by the user `Artist` subclass) return `Expression` objects from every predicate method. Compose them with `andExp` / `orExp`:
```java
import org.apache.cayenne.exp.Expression;
Expression e = Artist.ARTIST_NAME.eq("Picasso")
.andExp(Artist.DATE_OF_BIRTH.gt(someDate));
ObjectSelect.query(Artist.class).where(e).select(ctx);
```
This is the canonical pattern — see Cayenne's own test suites for many real-world examples (`Artist.ARTIST_NAME.eq(...)`, `Painting.TO_ARTIST.eq(artist)`, etc.). It's type-checked, refactor-safe, and survives column renames in the model.
For relationship traversal, chain `Property` constants with `.dot(...)`:
```java
Expression hasGuernica = Artist.PAINTINGS.dot(Painting.PAINTING_TITLE).eq("Guernica");
```
### Fallback: `ExpressionFactory` (dynamic field names)
Only when field selection is dynamic at runtime (e.g., the user picks a column from a UI dropdown):
```java
import org.apache.cayenne.exp.ExpressionFactory;
String runtimeField = pickField(); // not known at compile time
Expression e = ExpressionFactory.matchExp(runtimeField, value);
```
Avoid `ExpressionFactory.*` for static queries — `Property` constants are strictly better.
### Fallback: `Expression.fromString(...)` (parsed string form)
```java
Expression e = Expression.fromString("artistName = 'Picasso' and dateOfBirth > $cutoff");
e = e.params(Map.of("cutoff", someDate));
```
Supports `=`, `!=`, `<`, `>`, `<=`, `>=`, `like`, `likeIgnoreCase`, `in`, `between`, `and`, `or`, `not`, `null`. Dotted paths traverse relationships: `paintings.gallery.galleryName = 'MoMA'`. Useful when expressions are stored as strings (e.g. in config); otherwise prefer Property constants.
## Anti-patterns
- **String concatenation in raw SQL.** Always use `#bind($name)`. Concatenation is a SQL injection vector.
- **Using `selectOne` when multiple may match.** It throws. Use `selectFirst` if "any one" is okay.
- **Loading large result sets without `pageSize` or `iterator()`.** Both methods avoid materializing the full list.
- **N+1 from missing prefetch.** If you iterate `artists` and access `artist.getPaintings()` per artist, you'll fire one query per artist. Use `.prefetch(...)`.