Is JPA a specification or implementation?

15 Apr.,2024

 

You will get efficient and thoughtful service from KSQ Technology.

Advanced Spring Data JPA - Specifications and Querydsl

In my last blog post I introduced the basic feature set of Spring Data JPA. In this post I'd like to dive into some more features and how they can help you simplify data access layer implementation even further. The Spring Data repository abstraction consists of an interface based programming model, some factory classes and a Spring namespace to easily configure the infrastructure. A typical repository interface looks something like this:

public interface CustomerRepository extends JpaRepository<Customer, Long> {

  Customer findByEmailAddress(String emailAddress);

  List<Customer> findByLastname(String lastname, Sort sort);

  Page<Customer> findByFirstname(String firstname, Pageable pageable);
}

The first method simply expects to find a single customer with a given email address, the second method returns all customers with a given lastname and applies the given Sort to the result whereas the third method returns a Page of customers. For details have a look at the former blog post.

Although this approach is really convenient (you don't even have to write a single line of implementation code to get the queries executed) it has two drawbacks: first, the number of query methods might grow for larger applications because of - and that's the second point - the queries define a fixed set of criterias. To avoid these two drawbacks, wouldn't it be cool if you could come up with a set of atomic predicates that you could combine dynamically to build your query?

If you are a long time JPA user you might answer: isn't that what the Criteria API is for? Right, so let's have a look what a sample business requirement implementation could look like using the JPA Criteria API. Here's the use case: on their birthday's we want to send a voucher to all long term customers. How do we retrieve the ones that match?

We pretty much have two parts to the predicate: the birthday as well as what we call long-term-customer. Let's assume the latter means that the customer account was created at least two years ago. Here's how it would look like implemented using the JPA 2.0 Criteria API.

LocalDate today = new LocalDate();

CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Customer> query = builder.createQuery(Customer.class);
Root<Customer> root = query.from(Customer.class);

Predicate hasBirthday = builder.equal(root.get(Customer_.birthday), today);
Predicate isLongTermCustomer = builder.lessThan(root.get(Customer_.createdAt), today.minusYears(2); 
query.where(builder.and(hasBirthday, isLongTermCustomer));
em.createQuery(query.select(root)).getResultList();

What do we have here? We create a new LocalDate for convenience and go on with three lines of boilerplate to set up the necessary JPA infrastructure instances. Then we have two lines building the predicates, one to concatenate both and a last one to execute the actual query. We're using the meta-model classes introduced with JPA 2.0 and generated by the Annotation Processing API. The main problem with this code is that the predicates are not easy to externalize and reuse because you need to set up the CriteriaBuilder, CriteriaQuery and Root first. Also, readability of the code is poor as it is hard to quickly infer the intent of the code upon first glance.

Specifications

To be able to define reusable Predicates we introduced the Specification interface that is derived from concepts introduced in Eric Evans' Domain Driven Design book. It defines a specification as a predicate over an entity which is exactly what our Specification interface represents. The actually only consists of a single method:

public interface Specification<T> {
  Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb);
}

so we can now easily use a helper class like this:

public CustomerSpecifications {

  public static Specification<Customer> customerHasBirthday() {
    return new Specification<Customer> {
      public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) {
        return cb.equal(root.get(Customer_.birthday), today);
      }
    };
  }

  public static Specification<Customer> isLongTermCustomer() {
    return new Specification<Customer> {
      public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) {
        return cb.lessThan(root.get(Customer_.createdAt), new LocalDate.minusYears(2));
      }
    };
  }
}

Admittedly, not the most beautiful code in the world but it serves our initial requirement quite nicely: we can refer to a set of atomic specifications. The next question is: how will we execute these specifications? To do so, you simply extend JpaSpecificationExecutor in your repository interface and thus "pull in" an API to execute Specifications:

public interface CustomerRepository extends JpaRepository<Customer>, JpaSpecificationExecutor {
  // Your query methods here
}

A client can now do:

customerRepository.findAll(hasBirthday());
customerRepository.findAll(isLongTermCustomer());

The basic repository implementation will prepare the CriteriaQuery, Root and CriteriaBuilder for you, apply the Predicate created by the given Specification and execute the query. But couldn't we just have created simple query methods to achieve that? Correct, but remember our second initial requirement. We wanted to be able to freely combine atomic Specifications to create new ones one the fly. To do so we have a helper class Specifications that provides and(…) and or(…) methods to concatenate atomic Specifications. There's also a where(…) that provides some syntactic sugar to make the expression more readable. The use case sample I came up with in the beginning looks like this:

customerRepository.findAll(where(customerHasBirthday()).and(isLongTermCustomer()));

This reads fluently, improving readability as well as providing additional flexibility as compared to the use of the JPA Criteria API alone. The only caveat here is that coming up with the Specification implementation requires quite some coding effort.

Querydsl

To cure that pain an open-source project called Querydsl has come up with a quite similar but also different approach. Just like the JPA Criteria API it uses a Java 6 annotation processor to generate meta-model objects but produces a much more approachable API. Another cool thing about the project is that it has not only has support for JPA but also allows querying Hibernate, JDO, Lucene, JDBC and even plain collections.

So to get that up and running you add Querydsl to your pom.xml and configure the APT plugin accordingly.

<plugin>
  <groupId>com.mysema.maven</groupId>
  <artifactId>maven-apt-plugin</artifactId>
  <version>1.0</version>
  <executions>
    <execution>
      <phase>generate-sources</phase>
      <goals>
        <goal>process</goal>
      </goals>
      <configuration>
        <outputDirectory>target/generated-sources</outputDirectory>
        <processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
      </configuration>
    </execution>
  </executions>
</plugin>

This will cause your build to create special query classes - QCustomer inside the very same package in our case.

QCustomer customer = QCustomer.customer;
LocalDate today = new LocalDate();
BooleanExpression customerHasBirthday = customer.birthday.eq(today);
BooleanExpression isLongTermCustomer = customer.createdAt.lt(today.minusYears(2));

This is not only almost fluent English out of the box, the BooleanExpressions are even reusable without further wrapping which lets us get rid off the additional (and a bit ugly to implement) Specification wrapper. A further plus is that you get IDE code completion at every dot on the right hand side of the assignments, so customer. + CTRL + SPACE would list all properties. customer.birthday. + CTRL + SPACE would list all available keywords and so on. To execute Querydsl predicates you simply let your repository extend QueryDslPredicateExecutor:

public interface CustomerRepository extends JpaRepository<Customer>, QueryDslPredicateExecutor {
  // Your query methods here
}

Clients can then simply do:

BooleanExpression customerHasBirthday = customer.birthday.eq(today);
BooleanExpression isLongTermCustomer = customer.createdAt.lt(today.minusYears(2));
customerRepository.findAll(customerHasBirthday.and(isLongTermCustomer));

Summary

Spring Data JPA repository abstraction allows executing predicates either via JPA Criteria API predicates wrapped into a Specification object or via Querydsl predicates. To enable this functionality you simply let your repository extend JpaSpecificationExecutor or QueryDslPredicateExecutor (you could even use both side by side if you liked). Note that you need the Querydsl JARs in the class in case you decide for the Querydsl approach.

One more thing

One more cool thing about the Querydsl approach is that it is not only available for our JPA repositories but for our MongoDB support as well. The functionality is included in the just released M2 release of Spring Data MongoDB already. Beyond that both the Mongo and JPA module of Spring Data are supported on the CloudFoundry platform. See the cloudfoundry-samples wiki for getting started with Spring Data and CloudFoundry.

Specifications

JPA 2 introduces a criteria API that you can use to build queries programmatically. By writing a criteria, you define the where clause of a query for a domain class. Taking another step back, these criteria can be regarded as a predicate over the entity that is described by the JPA criteria API constraints.

Spring Data JPA takes the concept of a specification from Eric Evans' book, “Domain Driven Design”, following the same semantics and providing an API to define such specifications with the JPA criteria API. To support specifications, you can extend your repository interface with the JpaSpecificationExecutor interface, as follows:

public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
 …
}

The additional interface has methods that let you run specifications in a variety of ways. For example, the findAll method returns all entities that match the specification, as shown in the following example:

List<T> findAll(Specification<T> spec);

The Specification interface is defined as follows:

public interface Specification<T> {
  Predicate toPredicate(Root<T> root, CriteriaQuery<?> query,
            CriteriaBuilder builder);
}

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 JpaRepository without the need to declare a query (method) for every needed combination, as shown in the following example:

Example 1. Specifications for a Customer

public class CustomerSpecs {


  public static Specification<Customer> isLongTermCustomer() {
    return (root, query, builder) -> {
      LocalDate date = LocalDate.now().minusYears(2);
      return builder.lessThan(root.get(Customer_.createdAt), date);
    };
  }

  public static Specification<Customer> hasSalesOfMoreThan(MonetaryAmount value) {
    return (root, query, builder) -> {
      // build query here
    };
  }
}

The Customer_ type is a metamodel type generated using the JPA Metamodel generator (see the Hibernate implementation’s documentation for an example). So the expression, Customer_.createdAt, assumes the Customer has a createdAt attribute of type Date. 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. Using a simple Specification

List<Customer> customers = customerRepository.findAll(isLongTermCustomer());

Why not create a query for this kind of data access? Using a single Specification does not gain a lot of benefit over a plain query declaration. The power of specifications really shines when you combine them to create new Specification objects. You can achieve this through the default methods of Specification we provide to build expressions similar to the following:

Example 3. Combined Specifications

MonetaryAmount amount = new MonetaryAmount(200.0, Currencies.DOLLAR);
List<Customer> customers = customerRepository.findAll(
  isLongTermCustomer().or(hasSalesOfMoreThan(amount)));

Specification offers some “glue-code” default methods to chain and combine Specification instances. These methods let you extend your data access layer by creating new Specification implementations and combining them with already existing implementations.

And with JPA 2.1, the CriteriaBuilder API introduced CriteriaDelete. This is provided through JpaSpecificationExecutor’s `delete(Specification) API.

Example 4. Using a Specification to delete entries.

Specification<User> ageLessThan18 = (root, query, cb) -> cb.lessThan(root.get("age").as(Integer.class), 18)

userRepository.delete(ageLessThan18);

The Specification builds up a criteria where the age field (cast as an integer) is less than 18. Passed on to the userRepository, it will use JPA’s CriteriaDelete feature to generate the right DELETE operation. It then returns the number of entities deleted.

Is JPA a specification or implementation?

Specifications :: Spring Data JPA

Please visit our website for more information on this topic.

If you are looking for more details, kindly visit 5 Inch DTH Hammer.