Skip to content

Latest commit

 

History

History
124 lines (103 loc) · 5.15 KB

README.md

File metadata and controls

124 lines (103 loc) · 5.15 KB

Composite Specification API

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.

Example

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

Maven

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>

License

Distributed under the MIT license. See LICENSE for more information.