Spring Data JPA and the Specification Pattern

Advertisements

Spring Data JPA is a very useful module provided in the Spring Framework (Spring boot) to access the persistence layer with minimum effort and reduce a lot of boiler plate code using JPA. It allows to create queries with different approaches, for example:

  • Derived query methods (query creation from method names).
  • @Query annotation (written native SQL or JPQL queries).

In addition to those common approaches, there is a way to create queries dynamically using the Specification Pattern already provided by Spring JPA and taking advantage of the JPA Criteria API.

What’s the Specification Pattern?

Given an object sometimes is necessary to apply different conditions to a query, requiring to create a lot of different methods for each possible combination. The Specification Pattern derives from concepts introduced in Eric Evans’ Domain Driven Design book. It provides a design pattern that allows us to separate the search criteria from the object that performs the search. Let’s see an example.

There is a pool of different creatures and we often need to select some subset of them. We can write our search specification such as “creatures that can fly”, “creatures heavier than 500 kilograms”, or as a combination of other search specifications, and then give it to the party that will perform the filtering.

Why to use the Specification Pattern?

Using the Repository pattern provided by Spring Data JPA and its capabilities, it is common to start adding new method definitions for each different query that will be needed on the application and business logic. For entities with a lot of attributes/fields the Repository might end up with a bunch of different combination of required queries, all of them in a separate method, therefore our class will grow and contain dozens of methods – even almost one hundred in some cases -.

interface ProductRepository extends JpaRepository<Product, String>{
  Optional<Product> findById(long id);
  List<Product> findAllByNameLike(String name);
  List<Product> findAllByNameLikeAndPriceLessThanEqual(String name, Double price);
  List<Product> findAllByCategoryInAndPriceLessThanEqual(List<Category> categories, Double price);
  List<Product> findAllByCategoryInAndPriceBetween(List<Category> categories, Double bottom,Double top);
  List<Product> findAllByNameLikeAndCategoryIn(String name, List<Category> categories);
  List<Product> findAllByNameLikeAndCategoryInAndPriceBetween(String name, List<Category> categories, Double bottom, Double top);
  // more methods
  .....
  List<Product> findAllByCategoryInAndPriceGreaterThanEqual(List<Category> categories, Double price);
  ...
  //more methods 
  List<Product> findAllByNameLikeAndCategoryInAndPriceBetweenAndManufacturingPlace_State(
  String name, List<Category> categories, Double bottom, Double top, STATE state);  
}

In terms of productivity that scenario is ok, as a developer I can create in a a few seconds a method that access to the database filtering by some specific fields and returning values in Java, and we as developer will focus on the features and business logic. However, when it comes to readability and maintainability that scenario, a class with dozens (not to say almost hundred of methods) is a nightmare. And because of the naming conventions of Spring Data JPA we might have methods with incomprehensible names.

List<Product> findAllByNameLikeAndCategoryInAndPriceBetweenAndManufacturingPlace_State(
  String name, List<Category> categories, Double bottom, Double top, STATE state);

That’s why! the Specification pattern is a good solution to enhance readability and maintainability in our code, with minimum of boiler plate code, and reusing existing code.

Using the Specification Pattern in Spring Data JPA

Spring already has an interface Specification to implement it and make the different specifications reusable in our code base. As we can see, it’s based on the JPA Criteria API.

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

Creating the custom specifications

We can then create all the different custom specification based on what we need in the queries our application needs. Fortunately, we can use Lambda expressions to simplify the implementation of each specification.

public class CustomerSpecifications {
  
  public static Specification<Product> belongsToCategory(List<Category> categories){
  	return (root, query, criteriaBuilder)-> 
      criteriaBuilder.in(root.get(Product_.CATEGORY)).value(categories); 
  
  public static Specification<Product> priceGreaterThan(double price){
    return (root, query, criteriaBuilder) -> 
      criteriaBuilder.greaterThanOrEqualTo(root.get(Product_.PRICE), price);
  }
    
  public static Specification<Product> nameEquals(String name){
    return (root, query, criteriaBuilder) -> 
      criteriaBuilder.equal(root.get(Product_.NAME), name);
  }  
    
  public static Specification<Product> expired(){
    return (root, query, criteriaBuilder) -> 
      criteriaBuilder.lessThan(root.get(Product_.EXPIRATION_DATE), LocalDate.now());
  }
}

The Specification interface also has the public static helper methods and(), or(), and where() that allow us to combine multiple specifications. It also provides a not() method which allows us to negate a Specification.

Advertisements

Using the Custom Specifications

By implementing the interface JpaSpecificationExecutor in the Repository class, the repository now has some methods to query using Specifications. Those methods will replace the bunch of methods for each criteria combination we need.

List<T> findAll(Specification<T> spec);
Page<T> findAll(Specification<T> spec, Pageable pageable);
List<T> findAll(Specification<T> spec, Sort sort);

Easy peasy, just lets add that interface to the Repository interface.

public interface ProductRepository extends JpaRepository<Product>, JpaSpecificationExecutor {
  // Your query methods here
}

And then let’s use the custom specifications with the findAll methods of the Repository.

import static CustomerSpecifications.*;

private ProductRepository productRepository; 

public List<Product> getPremiumProducts(String name, List<Category> categories) {
  return productRepository.findAll(
    where(belongsToCategory(categories))
    .and(nameLike(name))
    .and(isPremium())
    .not(expired()));
}

We can reuse the created Custom Specification in different parts of the business logic, and combine them depending on the needs of that requirement by using the AND, OR, NOT, WHERE operations.

When Should I use Specifications over Query methods?

It depends on the characteristic of each application, if the entities have just a few fields and the combination of different queries for those fields is small, then It’s totally acceptable to use query methods for that purpose, at the end the code is still maintainable and readable, and query methods speed up our productivity to deliver and satisfy the application needs.

However, if the entities in the application has several fields, and in the practice we are doing several different queries by different fields, for sure we might end up with a Repository full of query methods (dozens) and some of them with incomprehensible names (due to the naming convention of Spring Data JPA). Therefore, we face a good candidate to implement the Specification Pattern, and finally improving the readability and maintainability of the code.

Conclusion

The Specification Pattern is not suitable for all the cases, it requires some effort creating the custom specifications, and for sure for many applications the approach of using Query derived from the method name, or the @Query annotation is good enough. And implementing this pattern might be seen as overkilling and unnecessary.

But, there is a number of applications where because of theirs characteristics the amount of queries can grow affecting the readability and maintainability of the code, and the Specification Pattern will help them to reduce and reuse the code. Using Spring JPA Data, we can take advantage of the Specification pattern with Criteria API that they already provided.

Hopefully this article will help those case where the Specification Pattern is a good candidate.

References

Advertisements

Leave a Reply

Your email address will not be published.