This project provides a composite Specification in the sense of "Specifications" by Eric Evans and Martin Fowler. It is parametrized with the type of a target the Predicate evaluates on. Instances of this class are composable in opposite to the Root limited Specification interface. The Specification API is a part of Spring Data JPA.
Consider the following entities:
@Entity
public class Employee {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private Department department;
@Column
private String firstName;
@Column
private String secondName;
@Column
private LocalDate dateOfBirth;
(...)
}
@Entity
public class Department {
@Id
private Long id;
@Column
private String name;
@OneToMany(mappedBy = "department", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private List<Employee> employees;
(...)
}
and the specifications:
public final class EmployeeSpecifications {
public static <S extends Path<Employee>> CompositeSpecification<Employee, S> firstName(String firstName) {
return CompositeSpecification.<Employee, S, TypeSafePredicateBuilder<Path<Employee>>>of(
(root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("firstName"), firstName)
);
}
public static <S extends Path<Employee>> CompositeSpecification<Employee, S> secondName(String secondName) {
return CompositeSpecification.<Employee, S, TypeSafePredicateBuilder<Path<Employee>>>of(
(root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("secondName"), secondName)
);
}
public static <S extends Path<Employee>> CompositeSpecification<Employee, S> dateOfBirth(CompositeSpecification<?, ? super Path<LocalDate>> dateOfBirthSpecification) {
return CompositeSpecification.<Employee, S, TypeSafePredicateBuilder<Path<Employee>>>of(
(root, query, criteriaBuilder) -> dateOfBirthSpecification.asBuilder().toPredicate(root.get("dateOfBirth"), query, criteriaBuilder)
);
}
}
public final class DepartmentSpecifications {
public static <S extends Path<Department>> CompositeSpecification<Department, S> name(String name) {
return CompositeSpecification.<Department, S, TypeSafePredicateBuilder<Path<Department>>>of(
(root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("name"), name)
);
}
public static <S extends From<?, Department>> CompositeSpecification<Department, S> joinEmployees(CompositeSpecification<?, ? super Join<?, Employee>> employeeSpecification) {
return CompositeSpecification.<Department, S, TypeSafePredicateBuilder<From<?, Department>>>of(
(root, query, criteriaBuilder) -> {
query.distinct(true);
return employeeSpecification.asBuilder().toPredicate(root.<Department, Employee>join("employees", JoinType.LEFT), query, criteriaBuilder);
});
}
public static <S extends From<?, Department>> CompositeSpecification<Department, S> fetchEmployees(CompositeSpecification<?, ? super Join<?, Employee>> employeeSpecification) {
return CompositeSpecification.<Department, S, TypeSafePredicateBuilder<From<?, Department>>>of(
(root, query, criteriaBuilder) -> {
query.distinct(true);
return employeeSpecification.asBuilder().toPredicate((Join<Department, Employee>) root.<Department, Employee>fetch("employees", JoinType.LEFT), query, criteriaBuilder);
});
}
}
To find departments that have an employee whose first name is John and was born before 1.01.1990, compose the specifications as follows:
var departments = departmentRepository.findAll(joinEmployees(firstName("John").and(dateOfBirth(lessThan(LocalDate.of(1990, 1, 1))))));
This technique allows to fetch the lazy associations on a per-query basis.
var department = departmentRepository.findOne(name("Sales").and(fetchEmployees(secondName("Doe"))));
More examples can be found here. Run the demo application with
mvn org.springframework.boot:spring-boot-maven-plugin:run -P example
To use this library, add the following dependency to your pom.xml:
<dependency>
<groupId>io.github.bartoszpop</groupId>
<artifactId>composite-specification</artifactId>
<version>1.0.0</version>
</dependency>
Distributed under the MIT license. See LICENSE for more information.