Provide Specification allOf and anyOf operators to help compose Specifications..
See #1943. Original PR: #404.
This commit is contained in:
committed by
Greg L. Turnquist
parent
624c6c681f
commit
2e5ed1d0ec
@@ -21,6 +21,8 @@ import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -33,6 +35,7 @@ import org.springframework.lang.Nullable;
|
||||
* @author Sebastian Staudt
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
* @author Daniel Shuy
|
||||
*/
|
||||
public interface Specification<T> extends Serializable {
|
||||
|
||||
@@ -98,4 +101,46 @@ public interface Specification<T> extends Serializable {
|
||||
*/
|
||||
@Nullable
|
||||
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
|
||||
|
||||
/**
|
||||
* Applies an AND operation to all the given {@link Specification}s.
|
||||
*
|
||||
* @param specifications The {@link Specification}s to compose. Can contain {@code null}s.
|
||||
* @return The conjunction of the specifications
|
||||
* @see #and(Specification)
|
||||
*/
|
||||
static <T> Specification<T> allOf(Iterable<Specification<T>> specifications) {
|
||||
|
||||
return StreamSupport.stream(specifications.spliterator(), false) //
|
||||
.reduce(Specification.where(null), Specification::and);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #allOf(Iterable)
|
||||
*/
|
||||
@SafeVarargs
|
||||
static <T> Specification<T> allOf(Specification<T>... specifications) {
|
||||
return allOf(Arrays.asList(specifications));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies an OR operation to all the given {@link Specification}s.
|
||||
*
|
||||
* @param specifications The {@link Specification}s to compose. Can contain {@code null}s.
|
||||
* @return The disjunction of the specifications
|
||||
* @see #or(Specification)
|
||||
*/
|
||||
static <T> Specification<T> anyOf(Iterable<Specification<T>> specifications) {
|
||||
|
||||
return StreamSupport.stream(specifications.spliterator(), false) //
|
||||
.reduce(Specification.where(null), Specification::or);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #anyOf(Iterable)
|
||||
*/
|
||||
@SafeVarargs
|
||||
static <T> Specification<T> anyOf(Specification<T>... specifications) {
|
||||
return anyOf(Arrays.asList(specifications));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.mockito.quality.Strictness;
|
||||
* @author Sebastian Staudt
|
||||
* @author Jens Schauder
|
||||
* @author Mark Paluch
|
||||
* @author Daniel Shuy
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@@ -118,6 +119,42 @@ class SpecificationUnitTests implements Serializable {
|
||||
assertThat(specification.toPredicate(root, query, builder)).isEqualTo(predicate);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1651
|
||||
public void allOfConcatenatesNull() {
|
||||
|
||||
Specification<Object> specification = Specification.allOf(null, spec, null);
|
||||
|
||||
assertThat(specification).isNotNull();
|
||||
assertThat(specification.toPredicate(root, query, builder)).isEqualTo(predicate);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1651
|
||||
public void anyOfConcatenatesNull() {
|
||||
|
||||
Specification<Object> specification = Specification.anyOf(null, spec, null);
|
||||
|
||||
assertThat(specification).isNotNull();
|
||||
assertThat(specification.toPredicate(root, query, builder)).isEqualTo(predicate);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1651
|
||||
public void emptyAllOfReturnsEmptySpecification() {
|
||||
|
||||
Specification<Object> specification = Specification.allOf();
|
||||
|
||||
assertThat(specification).isNotNull();
|
||||
assertThat(specification.toPredicate(root, query, builder)).isNull();
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1651
|
||||
public void emptyAnyOfReturnsEmptySpecification() {
|
||||
|
||||
Specification<Object> specification = Specification.anyOf();
|
||||
|
||||
assertThat(specification).isNotNull();
|
||||
assertThat(specification.toPredicate(root, query, builder)).isNull();
|
||||
}
|
||||
|
||||
@Test // DATAJPA-523
|
||||
void specificationsShouldBeSerializable() {
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
* @author Jesse Wouters
|
||||
* @author Greg Turnquist
|
||||
* @author Diego Krupitza
|
||||
* @author Daniel Shuy
|
||||
*/
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration("classpath:application-context.xml")
|
||||
@@ -489,12 +490,21 @@ public class UserRepositoryTests {
|
||||
.isThrownBy(() -> repository.findOne(userHasFirstnameLike("e")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // DATAJPA-1651
|
||||
void executesCombinedSpecificationsCorrectly() {
|
||||
|
||||
flushTestUsers();
|
||||
Specification<User> spec = userHasFirstname("Oliver").or(userHasLastname("Arrasz"));
|
||||
assertThat(repository.findAll(spec)).hasSize(2);
|
||||
Specification<User> spec1 = userHasFirstname("Oliver").or(userHasLastname("Arrasz"));
|
||||
List<User> users1 = repository.findAll(spec1);
|
||||
assertThat(users1).hasSize(2);
|
||||
|
||||
Specification<User> spec2 = Specification.anyOf( //
|
||||
userHasFirstname("Oliver"), //
|
||||
userHasLastname("Arrasz"));
|
||||
List<User> users2 = repository.findAll(spec2);
|
||||
assertThat(users2).hasSize(2);
|
||||
|
||||
assertThat(users1).containsExactlyInAnyOrderElementsOf(users2);
|
||||
}
|
||||
|
||||
@Test // DATAJPA-253
|
||||
@@ -506,16 +516,27 @@ public class UserRepositoryTests {
|
||||
assertThat(repository.findAll(spec)).containsOnly(secondUser);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // DATAJPA-1651
|
||||
void executesCombinedSpecificationsWithPageableCorrectly() {
|
||||
|
||||
flushTestUsers();
|
||||
Specification<User> spec = userHasFirstname("Oliver").or(userHasLastname("Arrasz"));
|
||||
Specification<User> spec1 = userHasFirstname("Oliver").or(userHasLastname("Arrasz"));
|
||||
|
||||
Page<User> users = repository.findAll(spec, PageRequest.of(0, 1));
|
||||
assertThat(users.getSize()).isEqualTo(1);
|
||||
assertThat(users.hasPrevious()).isFalse();
|
||||
assertThat(users.getTotalElements()).isEqualTo(2L);
|
||||
Page<User> users1 = repository.findAll(spec1, PageRequest.of(0, 1));
|
||||
assertThat(users1.getSize()).isEqualTo(1);
|
||||
assertThat(users1.hasPrevious()).isFalse();
|
||||
assertThat(users1.getTotalElements()).isEqualTo(2L);
|
||||
|
||||
Specification<User> spec2 = Specification.anyOf( //
|
||||
userHasFirstname("Oliver"), //
|
||||
userHasLastname("Arrasz"));
|
||||
|
||||
Page<User> users2 = repository.findAll(spec2, PageRequest.of(0, 1));
|
||||
assertThat(users2.getSize()).isEqualTo(1);
|
||||
assertThat(users2.hasPrevious()).isFalse();
|
||||
assertThat(users2.getTotalElements()).isEqualTo(2L);
|
||||
|
||||
assertThat(users1).containsExactlyInAnyOrderElementsOf(users2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -602,14 +623,14 @@ public class UserRepositoryTests {
|
||||
assertThat(repository.count()).isEqualTo(3L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // DATAJPA-1651
|
||||
void executesPagedSpecificationsCorrectly() {
|
||||
|
||||
Page<User> result = executeSpecWithSort(Sort.unsorted());
|
||||
assertThat(result.getContent()).isSubsetOf(firstUser, thirdUser);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // DATAJPA-1651
|
||||
void executesPagedSpecificationsWithSortCorrectly() {
|
||||
|
||||
Page<User> result = executeSpecWithSort(Sort.by(Direction.ASC, "lastname"));
|
||||
@@ -617,7 +638,7 @@ public class UserRepositoryTests {
|
||||
assertThat(result.getContent()).contains(firstUser).doesNotContain(secondUser, thirdUser);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test // DATAJPA-1651
|
||||
void executesPagedSpecificationWithSortCorrectly2() {
|
||||
|
||||
Page<User> result = executeSpecWithSort(Sort.by(Direction.DESC, "lastname"));
|
||||
@@ -2821,11 +2842,21 @@ public class UserRepositoryTests {
|
||||
|
||||
flushTestUsers();
|
||||
|
||||
Specification<User> spec = userHasFirstname("Oliver").or(userHasLastname("Matthews"));
|
||||
Specification<User> spec1 = userHasFirstname("Oliver").or(userHasLastname("Matthews"));
|
||||
|
||||
Page<User> result = repository.findAll(spec, PageRequest.of(0, 1, sort));
|
||||
assertThat(result.getTotalElements()).isEqualTo(2L);
|
||||
return result;
|
||||
Page<User> result1 = repository.findAll(spec1, PageRequest.of(0, 1, sort));
|
||||
assertThat(result1.getTotalElements()).isEqualTo(2L);
|
||||
|
||||
Specification<User> spec2 = Specification.anyOf( //
|
||||
userHasFirstname("Oliver"), //
|
||||
userHasLastname("Matthews"));
|
||||
|
||||
Page<User> result2 = repository.findAll(spec2, PageRequest.of(0, 1, sort));
|
||||
assertThat(result2.getTotalElements()).isEqualTo(2L);
|
||||
|
||||
assertThat(result1).containsExactlyElementsOf(result2);
|
||||
|
||||
return result2;
|
||||
}
|
||||
|
||||
private interface UserProjectionInterfaceBased {
|
||||
|
||||
Reference in New Issue
Block a user