title | layout |
---|---|
2.8.0 Release Notes |
page |
Criteria API: combine power of immutable objects with the flexibility of querying them
Immutables team is pleased to announce Immutables 2.8.0 release.
Major focus of this release was Criteria API which enables users to generate model-specific query DSL. Generated class (along with criteria runtime) allows accessing different backends in a unified, fluent and type-safe manner.
Benefits over raw driver API usage or string based abstractions (DSLs) are:
- Compile-time checking and type safety allows for much fewer mistakes
- IDE auto-completion guides through the choice of fields and operators
- Best in class readability due to drastically reduced number of parentheses and specially designed DNF approach
- Easier model refactoring
Benefits over existing frameworks like Spring Data, Morphia, QueryDSL or jOOQ are:
- Derive immutable implementation, query DSL, repository and more from a single definition
- Pluggable Sync / Async / Reactive execution models
- Pluggable backend implementations
- Generated or custom Repositories (aka DAOs) can be controlled to generate reading / writing or watching operations on entities
Define your model ...
@Value.Immutable
@Criteria // generate query DSL
@Criteria.Repository // generate repository for this model
interface Person {
String fullName();
int age();
Optional<String> nickName();
List<Pet> pets();
Optional<Friend> bestFriend();
// ...
}
... and start querying it with Criteria API
// basic query by ID
PersonCriteria.person.id.in("id1", "id2", "id3");
PersonCriteria.person.id.notIn("bad_id");
// more complex query on Strings, Comparables, Optionals and other nested Criterias
person
.fullName.is("John") // basic equal
.fullName.isNot("Mary") // not equal
.fullName.endsWith("Smith") // string condition
.fullName.is(3.1415D) // ERROR! will not compile since fullName is String (not double)
.nickName.isPresent() // for Optional attribute
.nickName.startsWith("Adam") // special matcher Optional<String> which is intersetion type between OptionalMatcher and StringMatcher
.pets.notEmpty() // condition on an Iterable
.active.isTrue() // boolean
.or() // disjunction (equivalent to logical OR)
.age.atLeast(21) // comparable attribute
.or()
.not(p -> p.nickName.hasLength(4)); // negation on a Optional<String> attribute
.bestFriend.value().hobby.endsWith("ing") // chaining criterias on other entities like Optional<Friend>
You will notice that there are no and
statements (conjunctions) that is because criteria uses
Disjunctive Normal Form (in short DNF) by default. Statements are
combined using logical and
unless disjunction or()
is explicitly used. One can still build complex logical expressions
by composing criterias using and
/ or
functions.
Typical ORDER BY
/ LIMIT
/ OFFSET
operations are part of API
// query datasource and return reactive type: Flowable
List<Person> persons = repository
.find(person.age.atLeast(33))
.orderBy(person.fullName.asc())
.offset(20)
.limit(10)
.fetch();
Projections and Aggregations (like count
/ min
/ max
/ sum
/ avg
) are also supported.
List<String> list = repository.findAll()
.orderBy(person.nickName.desc())
.groupBy(person.nickName)
.select(person.nickName, person.age.max(), person.age.min(), person.age.count(), person.age.sum())
.map((nickName, max, min, count, sum) -> ("nick=" + nickName.orElse(null) + " diff=" + (max - min) + " count=" + count + " sum=" + sum))) // type-safe projections
.fetch();
Backend is the bridge between criteria abstraction and native driver API (or queries). Instantiate a backend and attach it to a repository. It can then be used by your application. Note that backends can be exchanged without impacting existing repository usages.
// pluggable backends
Backend backend = new MongoBackend(...); // can be different backend (elastic, geode etc.)
PersonRepository repository = new PersonRepository(backend);
Out of the box, Criteria supports the following backends (you can also integrate your own):
- In-Memory Backend. Simple hashmap implementation on the top of ConcurrentMap
- Elastic Search
- MongoDB
- Apache Geode
- Bring Your Own Backend (BYOB)
Flexible stream processing models adaptable to various usecases:
- Synchronous. Returning List / Optional / void / etc.
- Asyncronous. Returning CompletionStage
- Reactive streams. Returning Publisher
- RxJava. Returning Flowable / Single / Maybe.
- Project Reactor. Returning Flux / Mono.
// one of RxJavaReadable/ SyncReadable/ AsyncReadable etc.
@Criteria.Repository(facets=RxJavaReadable.class)
interface Person {}
// return rxjava Flowable type
Flowable<Person> result = repository.find(person.active.isTrue()).fetch();
While we don't yet deprecate existing mongo repositories, new and current users are encouraged to consider criteria API as a better alternative.
Please follow official guide for more information.