{{announcement.body}}
{{announcement.title}}

Using Spring Data JPA Specification

DZone 's Guide to

Using Spring Data JPA Specification

Want to learn more about using Spring Data JPA? Check out this tutorial where we look at using the Specification API in Spring Data JPA.

· Java Zone ·
Free Resource

Spring Data JPA was created primarily to allow easy query creation with query generation by method name. However, sometimes, we need to create complex queries and cannot take advantage of a query generator.

The Spring Data JPA provides a repository programming model that starts with an interface per managed domain object. Defining these interfaces serves two purposes: first, by extending the JpaRepository, we get a bunch of generic CRUD methods, like save, findAll, delete, and so on. Second, this will allow the Spring Data JPA repository infrastructure to scan the classpath for this interface and create a Spring bean for it. A typical repository interface will look 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);
}


To Create Complex Queries, Why Specification?

Yes, complex queries can be build using the Criteria API. To understand why we should use specification, let’s consider a simple business requirement. We will implement this requirement using Criteria API and after with specification.

Here’s is the use case: on a customer’s birthday, we want to send a voucher to all long-term customers. How do we retrieve one that matches?

We have two predicates:

  • Birthday
  • Long-term customer — let us assume that the customer created an account at least two years ago.

Here’s how it would with the implementation 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();


In the above code,

  • The first line created LocalDate to compare the customer birthdate with today's date.
  • The following three lines contain boilerplate code to set up the necessary JPA infrastructure instance.
  • Then, in the next two lines, we are building the predicates
  • Out of the last two lines, one is used to concatenate both predicates, and the last one is used to execute the query.

The main problem with this code is that predicates are not easy to externalize and reuse because you need to set up the CriteriaBuilder, CriteriaQuery, and Root first. Also, code readability is poor because it is hard to quickly infer the intent of the code.

Specification

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. This actually only consists of a single method:

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


When using Java 8, the code becomes very clear and easy to understand.

public CustomerSpecifications {
  public static Specification<Customer> customerHasBirthday() {
return (root, query, cb) ->{ 
return cb.equal(root.get(Customer_.birthday), today);
};
 }

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


A client can now perform the following:

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


Here, the basic implementation will prepare the CriteriaQueryRoot, and CriteriaBuilder for you, apply the predicate created by the given specification, and execute the query.

We just created the reusable predicates that can be individually executed. We can combine these individual predicates to meet our business requirement. We have a helper class, specifications, that provides and(…)   and or(…) methods to concatenate atomic specifications.

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


This reads fluently, improving readability as well as providing additional flexibility when 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.

Here are some benefits of specifications:

  1. All "basic" queries are already implemented, i.e. findById, save, and delete
  2. Pagination works out of the box. You can pass a Pageable object simply from the Controller to the Service to your Repository, and it just works (even sorting)!
  3. Using Spring's Specification API is a little bit more simple than plain JPA stuff because you just create predicates and don't have to mess around with the EntityManager and PersistenceContext.

If you have anything that you want to add or share, then please leave a message in the comment section below.

Happy learning!

Topics:
spring data jpa ,specification pattern ,java ,tutorial ,specification ,spring

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}