/
CompositeSpecification.java
160 lines (148 loc) · 7.33 KB
/
CompositeSpecification.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package io.github.bartoszpop.jpa.specification;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
/**
* This class is a composite {@link Specification} in the sense of <a href="https://www.martinfowler.com/apsupp/spec.pdf">"Specifications"</a> by Eric Evans and Martin Fowler.
* <p>
* It is parameterized with a supertype of {@link Root} that it depends on to construct {@link Predicate}.
* <br>
* Because {@link CompositeSpecification#asBuilder()} returns {@link PredicateBuilder} applicable to this type, thus an instance of this class
* <br>
* may delegate to an instance parameterized with a different type argument.
* <p>
* Consider the following entities
* <pre>
* @Entity
* public class Employee {
*
* @ManyToOne(fetch = FetchType.LAZY)
* private Department department;
*
* @Column
* private String firstName;
*
* @Column
* private LocalDate dateOfBirth;
*
* (...)
* }
*
* @Entity
* public class Department {
*
* @OneToMany(mappedBy = "department", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
* private List<Employee> employees;
*
* (...)
* }
* </pre>
* and the specifications
* <pre>{@code
* public final class DepartmentSpecifications {
* private DepartmentSpecifications() {
* }
*
* public static TypeSafeSpecification<Department, From<?, Department>> joinEmployees(TypeSafeSpecification<?, ? super Join<?, Employee>> employeeSpecification) {
* return TypeSafeSpecification.<Department, From<?, Department>, TypeSafePredicateBuilder<From<?, Department>>> of(
* (root, query, criteriaBuilder) ->
* employeeSpecification.asBuilder().toPredicate(root.<Department, Employee>join("employees", JoinType.LEFT), query, criteriaBuilder));
* }
*
* public static TypeSafeSpecification<Department, From<?, Department>> fetchEmployees() {
* return TypeSafeSpecification.<Department, From<?, Department>, TypeSafePredicateBuilder<From<?, Department>>> of(
* (root, query, criteriaBuilder) -> {
* root.fetch("employees", JoinType.LEFT);
* return criteriaBuilder.and();
* });
* }
* }
*
* public final class EmployeeSpecifications {
*
* private EmployeeSpecifications() {
* }
*
* public static TypeSafeSpecification<Employee, Path<Employee>> name(String name) {
* return TypeSafeSpecification.<Employee, Path<Employee>, TypeSafePredicateBuilder<Path<Employee>>> of(
* (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("name"), name)
* );
* }
*
* public static TypeSafeSpecification<Employee, Path<Employee>> dateOfBirth(LocalDate dateOfBirth) {
* return TypeSafeSpecification.<Employee, Path<Employee>, TypeSafePredicateBuilder<Path<Employee>>> of(
* (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("dateOfBirth"), dateOfBirth)
* );
* }
* }
* }</pre>
* To find departments that have an employee whose first name is John and was born on 1.01.1990, compose the specifications as follows:
* <pre>{@code
* var departments = departmentRepository.findAll(joinEmployees(firstName("John").and(dateOfBirth(LocalDate.of(1990, 1, 1)))));
* }</pre>
* This technique allows to fetch the LAZY associations on a per-query basis.
* <pre>{@code
* var department = departmentRepository.findOne(name("Sales").and(fetchEmployees()));
* }</pre>
* See <a href="https://github.com/bartoszpop/composite-specification/tree/main/src/example/">Composite Specification</a> for more examples.
*
* @param <T> the type of the entity
* @param <S> the type of a target the predicate evaluates on
* @author Bartosz Popiela
*/
public final class CompositeSpecification<T, S> implements Specification<T> {
private final PredicateBuilder<? super S> predicateBuilder;
/**
* This constructor must be private because when parameterized with {@code <U extends PredicateBuilder<S> & TypeSafe<? super Root<T>>>},
* the Java compiler does not restrict the type arguments of {@link PredicateBuilder} if the constructor argument is a lambda expression,
* e.g. the type of lambda expression in {@code new TypeSafeSpecification<String, Number>((target, query, criteriaBuilder) -> {...})}
* is {@code PredicateBuilder<String> & TypeSafe<? super Root<Number>>}.
* <p>
* If {@link TypeSafe} is package-private, the above statement throws {@link IllegalAccessError}.
* <p>
* If {@link TypeSafe} is sealed and permits {@link TypeSafePredicateBuilder} only, it throws {@link IncompatibleClassChangeError}.
*/
private CompositeSpecification(PredicateBuilder<? super S> predicateBuilder) {
this.predicateBuilder = predicateBuilder;
}
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
// Cast allowed because only instances of TypeSafePredicateBuilder<S>, where S is a supertype of Root<T> may be passed to TypeSafeSpecification#of
//noinspection unchecked
return ((PredicateBuilder<? super Root<T>>) predicateBuilder).toPredicate(root, query, criteriaBuilder);
}
/**
* Creates an instance of {@code TypeSafeSpecification<T, S>}, where {@code S} is a supertype of {@code Root<T>}.
* This is because {@link TypeSafePredicateBuilder} is the only interface derived from {@link TypeSafe}
* and it implements {@link PredicateBuilder} and {@link TypeSafe} with the same type argument {@code T}.
*/
public static <T, S, U extends PredicateBuilder<? super S> & TypeSafe<? super Root<T>>> CompositeSpecification<T, S> of(U predicateBuilder) {
return new CompositeSpecification<>(predicateBuilder);
}
public PredicateBuilder<S> asBuilder() {
return predicateBuilder::toPredicate;
}
public static <T, S> CompositeSpecification<T, S> noOp() {
return new CompositeSpecification<>((root, query, criteriaBuilder) -> criteriaBuilder.and());
}
public static <T, S> CompositeSpecification<T, S> not(CompositeSpecification<T, S> specification) {
return new CompositeSpecification<>((TypeSafePredicateBuilder<S>) (root, query, criteriaBuilder) ->
criteriaBuilder.not(specification.asBuilder().toPredicate(root, query, criteriaBuilder)));
}
public CompositeSpecification<T, S> and(CompositeSpecification<T, ? super S> other) {
return new CompositeSpecification<>((root, query, criteriaBuilder) -> {
var left = this.asBuilder().toPredicate(root, query, criteriaBuilder);
var right = other.asBuilder().toPredicate(root, query, criteriaBuilder);
return criteriaBuilder.and(left, right);
});
}
public CompositeSpecification<T, S> or(CompositeSpecification<T, ? super S> other) {
return new CompositeSpecification<>((root, query, criteriaBuilder) -> {
var left = this.asBuilder().toPredicate(root, query, criteriaBuilder);
var right = other.asBuilder().toPredicate(root, query, criteriaBuilder);
return criteriaBuilder.or(left, right);
});
}
}