2.6. Specifications

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.