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 ProjectDependencies and Technologies Used: - spring-data-jpa 2.1.0.RELEASE: Spring Data module for JPA repositories.
Uses org.springframework:spring-context 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
|
|