The goal of Criteria module is to provide fluent java API (DSL) to query heterogeneous data-sources.
- Expressive and type-safe API Compile-type validation of the query.
- Dynamic Combine predicates at runtime based on some logic
- Data-source agnostic Define criteria once and apply to different data-sources (Map, JDBC, Mongo, Elastic etc.)
- Blocking / asynchronous operations Generated repositories allow you to query data in blocking, non-blocking and reactive fashion
Define your model using immutables interfaces
@Value.Immutable
@Criteria // generate criteria
@Criteria.Repository // means generate repository (different from @Criteria)
interface Person {
@Criteria.Id
String id();
String fullName();
Optional<String> nickName();
int age();
List<Pet> pets();
}
Because of @Criteria
annotation, PersonCriteria
class is automatically generated by immutables. You can now leverage it to write queries similar to:
// basic query by id
PersonCriteria.person.id.in("id1", "id2", "id3");
PersonCriteria.person.id.notIn("bad_id");
// query on Strings, Comparables and Optionals
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") // For Optional<String> attribute
.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>
// apply specific predicate to elements of a collection
person
.pets.none().type.is(Pet.PetType.iguana) // no Iguanas
.or()
.pets.any().name.contains("fluffy"); // person has a pet which sounds like fluffy
You will notice that there are no and
statements (conjunctions) that is because criteria uses
Disjunctive Normal Form (in short DNF) by default.
For more complex expressions, one can still combine criterias arbitrarily using and
s / or
s / not
s.
Statement like A and (B or C)
can be written as follows:
person.fullName.is("John").and(person.age.greaterThan(22).or().nickName.isPresent())
Not all entities require repository (@Criteria.Repository
) but you need to add @Criteria
to all classes you want to query by. For example, to filter on Person.pets.name
Pet
class needs to have @Criteria
(otherwise PersonCriteria.pets
will have a very generic Object matcher).
@Criteria.Repository
instructs immutables to generate repository class with find
/ insert
/ watch
operations. You are required to provide a valid backend
instance (mongo, elastic, inmemory etc).
MongoDatabase database = ... // prepare with CodecRegistry etc.
Backend backend = new MongoBackend(MongoSetup.of(database));
// PersonRepository is automatically generated. You need to provide only backend instance
PersonRepository repository = new PersonRepository(backend);
repository.insert(ImmutablePerson.builder().id("aaa").fullName("John Smith").age(22).build());
// query repository
List<Person> result = repository.find(person.fullName.contains("Smith")).fetch();
- Matcher Typed predicate on a particular attribute. There are several variations of the matcher and, usually, they're associated with a type (eg. StringMatcher, IterableMatcher, ComparableMatcher). Matcher internally builds an Expression.
- Expression Abstraction of a generic expression modeled as Abstract Syntax Tree. Used internally as Intermediate Representation (IR) to transform original expression into a native query of a database. Users rarely have to deal with this API unless they write adapters for a particular backend.
- Backend adapter to a data-source (database). Responsible for interpreting expressions and operations into native queries and API calls using vendor drivers.
- Repository User facing API to perform queries, updates, pub/sub or other CRUD operations. Uses Backend.
- Facet Property of repository to fine-tune its behaviour. Eg.
Readable
/Writable
/Watchable
Also one can define return types based on rxjava / async (CompletionStage) or synchronous types.
common
module contains runtime support. Remaining folders are backend and facet implementation.
This folder contains classes specific to Criteria API and its runtime evaluation:
common
shared classes by all moduleselasticsearch
adapter for Elastic Searchmongo
adapter for MongoDB based on reactive streams driver.geode
adapter for Apache Geodeinmemory
lightweight implementation of a backend based on existing Map.rxjava
rxjava repository facets.
Criteria API requires Java 8 (or later)