JPA 2 introduces a criteria API that can be used to build queries programatically. Writing a criteria you actually define the where-clause of a query for a query of the handled domain class. Taking another step back these criterias can be regarded as predicate over the entity that is verbalized by the JPA criteria API constraints.
Hades now takes the concept of a specification from Eric Evans'
book Domain Driven Design, that carries the same semantics and provides
an API to define such Specifications
using the JPA criteria API. Thus you find methods like this in
GenericDao:
List<T> readAll(Specification<T> spec);
The Specification interface now
looks as follows:
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<T> query,
CriteriaBuilder builder);
}Okay, so what is the typical use case?
Specifications can easily be used to
build an extensible set of predicates on top of an entity that then can
be combined and used with GenericDao
without the need of declaring a query (method) for every needed
combination of those. Here's an example:
Example 2.22. Specifications for a Customer
public class CustomerSpecs {
public static Specification<Customer> isLongTermCustomer() {
return new Specification<Customer>() {
Predicate toPredicate(Root<T> root, CriteriaQuery<T> query,
CriteriaBuilder builder) {
LocalDate date = new LocalDate().minusYears(2);
return builder.lessThan(root.get(Customer_.createdAt), date);
}
};
}
public static Specification<Customer> hasSalesOfMoreThan(MontaryAmount value) {
return new Specification<Customer>() {
Predicate toPredicate(Root<T> root, CriteriaQuery<T> query,
CriteriaBuilder builder) {
// build query here
}
};
}
}Admittedly the amount of boilerplate leaves room for improvement
(that will hopefully be reduced by Java 7 closures) but the client side
becomes much nicer as you will see below. Besides that we have expressed
some criteria on a business requirement abstraction level and created
executable Specifications. So a client
might use a Specification as
follows:
Example 2.23. Using a simple Specification
List<Customer> customers = customerDao.readAll(isLongTermCustomer());
Okay, why not simply creating a query for this kind of data
access? You're right. Using a single
Specification does not gain a lot of
benefit over a plain query declaration. The power of
Specifications really exposes when you
combine them to create new Specification
objects. You can achieve this through the
Specifications helper class Hades provides to
build expressions like this:
Example 2.24. Combined Specifications
MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR); List<Customer> customers = customerDao.readAll( where(isLongTermCustomer()).or(hasSalesOfMoreThan(amount)));
As
you can see, Specifications offers some
gluecode methods to chain and combine
Specifications. Thus extending your
data access layer is just a matter of creating new
Specification implementations and
combining them with ones already existing.