Get ahead
VMware offers training and certification to turbo-charge your progress.
Learn moreIn 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.
To be able to define reusable Predicate
s 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 Specification
s:
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 Specification
s 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 Specification
s. 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.
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 BooleanExpression
s 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));
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 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.