Close

Spring Data JPA - Using Specifications to execute JPA Criteria Queries

[Updated: Oct 12, 2018, Created: Oct 2, 2018]

Spring Data provides Specification interface which can be used to execute JPA criteria queries.

Following is Specification interface snippet:

package org.springframework.data.jpa.domain;
 ....
public interface Specification<T> extends Serializable {
	static <T> Specification<T> not(Specification<T> spec) { //Negates the given spec
		return Specifications.negated(spec);
	}
	static <T> Specification<T> where(Specification<T> spec) {//Applies Where clause the give spec
		return Specifications.where(spec);
	}
	default Specification<T> and(Specification<T> other) {//Applies AND condition to the give spec
		return Specifications.composed(this, other, AND);
	}
	default Specification<T> or(Specification<T> other) {//Applies OR condition to the give spec
		return Specifications.composed(this, other, OR);
	}
	@Nullable
	Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, 
                                          CriteriaBuilder criteriaBuilder);//Creates a Predicate
}

As seen above there's only one abstract method 'toPredicate()' which returns javax.persistence.criteria.Predicate. All other methods are helper methods to create composite Specifications. That means we only have to implement 'toPredicate()' method and return the Where Clause predicate, the rest of the stuff i.e. creating CriteriaBuilder, Root etc is automatically provided to us by Spring. Also we don't have to worry about executing criteria query via TypedQuery.getResultList(), Spring takes care of that as well.

To use Specifications we also have to extend our repository interface with JpaSpecificationExecutor interface. This interface provides methods to execute Specifications. Here's this interface snippet:

package org.springframework.data.jpa.repository;
 ......
public interface JpaSpecificationExecutor<T> {
	Optional<T> findOne(@Nullable Specification<T> spec);
	List<T> findAll(@Nullable Specification<T> spec);
	Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
	List<T> findAll(@Nullable Specification<T> spec, Sort sort);
	long count(@Nullable Specification<T> spec);
}

Let's see an example to understand how that works.

Example

Entities

@Entity
public class Employee {
  @Id
  @GeneratedValue
  private long id;
  private String name;
  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
  private List<Phone> phones;
    .............
}
@Entity
public class Phone {
  @Id
  @GeneratedValue
  private long id;
  private PhoneType type;
  private String number;
    .............
}
public enum PhoneType {
  Home,
  Cell,
  Work
}

Creating Specifications

public class EmployeeSpecs {
  public static Specification<Employee> getEmployeesByNameSpec(String name) {
      return new Specification<Employee>() {
          @Override
          public Predicate toPredicate(Root<Employee> root,
                                       CriteriaQuery<?> query,
                                       CriteriaBuilder criteriaBuilder) {
              Predicate equalPredicate = criteriaBuilder.equal(root.get(Employee_.name), name);
              return equalPredicate;
          }
      };
  }

  public static Specification<Employee> getEmployeesByPhoneTypeSpec(PhoneType phoneType) {
      return new Specification<Employee>() {
          @Override
          public Predicate toPredicate(Root<Employee> root,
                                       CriteriaQuery<?> query,
                                       CriteriaBuilder criteriaBuilder) {
              ListJoin<Employee, Phone> phoneJoin = root.join(Employee_.phones);
              Predicate equalPredicate = criteriaBuilder.equal(phoneJoin.get(Phone_.type), phoneType);
              return equalPredicate;
          }
      };
  }
}

Above code can be simplified by using Java 8 lambda:

public class EmployeeSpecs {
    public static Specification<Employee> getEmployeesByNameSpec(String name) {
        return (root, query, criteriaBuilder) -> {
            return criteriaBuilder.equal(root.get(Employee_.name), name);
        };
    }

    public static Specification<Employee> getEmployeesByPhoneTypeSpec(PhoneType phoneType) {
        return (root, query, criteriaBuilder) -> {
            ListJoin<Employee, Phone> phoneJoin = root.join(Employee_.phones);
            return criteriaBuilder.equal(phoneJoin.get(Phone_.type), phoneType);
        };
    }
}

Repository

public interface EmployeeRepository extends CrudRepository<Employee, Long>,
      JpaSpecificationExecutor<Employee> {
}

Example Client

@Component
public class ExampleClient {

  @Autowired
  private EmployeeRepository repo;

  public void run() {
      List<Employee> persons = createEmployees();
      repo.saveAll(persons);
      
      findAllEmployees();
      findEmployeesByName();
      findEmployeesByPhoneType();
  }

  private void findEmployeesByName() {
      System.out.println("-- finding employees with name Tim --");
      //calling JpaSpecificationExecutor#findAll(Specification)
      List<Employee> list = repo.findAll(EmployeeSpecs.getEmployeesByNameSpec("Tim"));
      list.forEach(System.out::println);
  }

  private void findEmployeesByPhoneType() {
      System.out.println("-- finding employees by phone type Cell --");
      //calling JpaSpecificationExecutor#findAll(Specification)
      List<Employee> list = repo.findAll(EmployeeSpecs.getEmployeesByPhoneTypeSpec(PhoneType.Cell));
      list.forEach(System.out::println);
  }

  private void findAllEmployees() {
      System.out.println(" -- getting all Employees --");
      Iterable<Employee> iterable = repo.findAll();
      List<Employee> allEmployees = StreamSupport.stream(iterable.spliterator(), false)
                                                 .collect(Collectors.toList());
      allEmployees.forEach(System.out::println);
  }

  private static List<Employee> createEmployees() {
      return Arrays.asList(Employee.create("Diana",
              Phone.of(PhoneType.Home, "111-111-111"), Phone.of(PhoneType.Work, "222-222-222")),
              Employee.create("Mike",
                      Phone.of(PhoneType.Work, "333-111-111"), Phone.of(PhoneType.Cell, "333-222-222")),
              Employee.create("Tim", Phone.of(PhoneType.Work, "444-111-111"), Phone
                      .of(PhoneType.Home, "444-222-222")),
              Employee.create("Jack", Phone.of(PhoneType.Cell, "555-222-222")));
  }
}

Main class

public class ExampleMain {

  public static void main(String[] args) {
      AnnotationConfigApplicationContext context =
              new AnnotationConfigApplicationContext(AppConfig.class);
      ExampleClient exampleClient = context.getBean(ExampleClient.class);
      exampleClient.run();
      EntityManagerFactory emf = context.getBean(EntityManagerFactory.class);
      emf.close();
  }
}
 -- getting all Employees --
Employee{id=1, name='Diana', phones=[Phone{id=2, type=Home, number='111-111-111'}, Phone{id=3, type=Work, number='222-222-222'}]}
Employee{id=4, name='Mike', phones=[Phone{id=5, type=Work, number='333-111-111'}, Phone{id=6, type=Cell, number='333-222-222'}]}
Employee{id=7, name='Tim', phones=[Phone{id=8, type=Work, number='444-111-111'}, Phone{id=9, type=Home, number='444-222-222'}]}
Employee{id=10, name='Jack', phones=[Phone{id=11, type=Cell, number='555-222-222'}]}
-- finding employees with name Tim --
Employee{id=7, name='Tim', phones=[Phone{id=8, type=Work, number='444-111-111'}, Phone{id=9, type=Home, number='444-222-222'}]}
-- finding employees by phone type Cell --
Employee{id=4, name='Mike', phones=[Phone{id=5, type=Work, number='333-111-111'}, Phone{id=6, type=Cell, number='333-222-222'}]}
Employee{id=10, name='Jack', phones=[Phone{id=11, type=Cell, number='555-222-222'}]}

Example Project

Dependencies and Technologies Used:

  • spring-data-jpa 2.1.0.RELEASE: Spring Data module for JPA repositories.
    Uses org.springframework:spring-co1ntext version 5.1.0.RELEASE
  • hibernate-core 5.3.6.Final: Hibernate's core ORM functionality.
    Implements javax.persistence:javax.persistence-api version 2.2
  • hibernate-jpamodelgen 5.3.6.Final: Annotation Processor to generate JPA 2 static metamodel classes.
  • h2 1.4.197: H2 Database Engine.
  • JDK 1.8
  • Maven 3.5.4

Spring Data JPA - Specifications Example Select All Download
  • spring-data-specifications-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • EmployeeSpecs.java
          • resources
            • META-INF

    See Also