Address review findings.

Original Pull Request: #3578
This commit is contained in:
Mark Paluch
2025-01-08 11:24:51 +01:00
parent 4a13c44697
commit 2e58485e06
11 changed files with 105 additions and 65 deletions

View File

@@ -31,6 +31,12 @@ import org.springframework.util.Assert;
/**
* Specification in the sense of Domain Driven Design to handle Criteria Deletes.
* <p>
* Specifications can be composed into higher order functions from other specifications using
* {@link #and(DeleteSpecification)}, {@link #or(DeleteSpecification)} or factory methods such as
* {@link #allOf(Iterable)}. Composition considers whether one or more specifications contribute to the overall
* predicate by returning a {@link Predicate} or {@literal null}. Specifications returning {@literal null} are
* considered to not contribute to the overall predicate and their result is not considered in the final predicate.
*
* @author Mark Paluch
* @since 4.0
@@ -44,7 +50,7 @@ public interface DeleteSpecification<T> extends Serializable {
* @param <T> the type of the {@link Root} the resulting {@literal DeleteSpecification} operates on.
* @return guaranteed to be not {@literal null}.
*/
static <T> DeleteSpecification<T> all() {
static <T> DeleteSpecification<T> unrestricted() {
return (root, query, builder) -> null;
}
@@ -150,13 +156,14 @@ public interface DeleteSpecification<T> extends Serializable {
return (root, delete, builder) -> {
Predicate not = spec.toPredicate(root, delete, builder);
return not != null ? builder.not(not) : null;
Predicate predicate = spec.toPredicate(root, delete, builder);
return predicate != null ? builder.not(predicate) : null;
};
}
/**
* Applies an AND operation to all the given {@link DeleteSpecification}s.
* Applies an AND operation to all the given {@link DeleteSpecification}s. If {@code specifications} is empty, the
* resulting {@link DeleteSpecification} will be unrestricted applying to all objects.
*
* @param specifications the {@link DeleteSpecification}s to compose.
* @return the conjunction of the specifications.
@@ -169,7 +176,8 @@ public interface DeleteSpecification<T> extends Serializable {
}
/**
* Applies an AND operation to all the given {@link DeleteSpecification}s.
* Applies an AND operation to all the given {@link DeleteSpecification}s. If {@code specifications} is empty, the
* resulting {@link DeleteSpecification} will be unrestricted applying to all objects.
*
* @param specifications the {@link DeleteSpecification}s to compose.
* @return the conjunction of the specifications.
@@ -179,11 +187,12 @@ public interface DeleteSpecification<T> extends Serializable {
static <T> DeleteSpecification<T> allOf(Iterable<DeleteSpecification<T>> specifications) {
return StreamSupport.stream(specifications.spliterator(), false) //
.reduce(DeleteSpecification.all(), DeleteSpecification::and);
.reduce(DeleteSpecification.unrestricted(), DeleteSpecification::and);
}
/**
* Applies an OR operation to all the given {@link DeleteSpecification}s.
* Applies an OR operation to all the given {@link DeleteSpecification}s. If {@code specifications} is empty, the
* resulting {@link DeleteSpecification} will be unrestricted applying to all objects.
*
* @param specifications the {@link DeleteSpecification}s to compose.
* @return the disjunction of the specifications.
@@ -196,7 +205,8 @@ public interface DeleteSpecification<T> extends Serializable {
}
/**
* Applies an OR operation to all the given {@link DeleteSpecification}s.
* Applies an OR operation to all the given {@link DeleteSpecification}s. If {@code specifications} is empty, the
* resulting {@link DeleteSpecification} will be unrestricted applying to all objects.
*
* @param specifications the {@link DeleteSpecification}s to compose.
* @return the disjunction of the specifications.
@@ -206,7 +216,7 @@ public interface DeleteSpecification<T> extends Serializable {
static <T> DeleteSpecification<T> anyOf(Iterable<DeleteSpecification<T>> specifications) {
return StreamSupport.stream(specifications.spliterator(), false) //
.reduce(DeleteSpecification.all(), DeleteSpecification::or);
.reduce(DeleteSpecification.unrestricted(), DeleteSpecification::or);
}
/**

View File

@@ -30,6 +30,12 @@ import org.springframework.util.Assert;
/**
* Specification in the sense of Domain Driven Design.
* <p>
* Specifications can be composed into higher order functions from other specifications using
* {@link #and(PredicateSpecification)}, {@link #or(PredicateSpecification)} or factory methods such as
* {@link #allOf(Iterable)}. Composition considers whether one or more specifications contribute to the overall
* predicate by returning a {@link Predicate} or {@literal null}. Specifications returning {@literal null} are
* considered to not contribute to the overall predicate and their result is not considered in the final predicate.
*
* @author Mark Paluch
* @since 4.0
@@ -42,7 +48,7 @@ public interface PredicateSpecification<T> extends Serializable {
* @param <T> the type of the {@link Root} the resulting {@literal PredicateSpecification} operates on.
* @return guaranteed to be not {@literal null}.
*/
static <T> PredicateSpecification<T> all() {
static <T> PredicateSpecification<T> unrestricted() {
return (root, builder) -> null;
}
@@ -104,13 +110,14 @@ public interface PredicateSpecification<T> extends Serializable {
return (root, builder) -> {
Predicate not = spec.toPredicate(root, builder);
return not != null ? builder.not(not) : null;
Predicate predicate = spec.toPredicate(root, builder);
return predicate != null ? builder.not(predicate) : null;
};
}
/**
* Applies an AND operation to all the given {@link PredicateSpecification}s.
* Applies an AND operation to all the given {@link PredicateSpecification}s. If {@code specifications} is empty, the
* resulting {@link PredicateSpecification} will be unrestricted applying to all objects.
*
* @param specifications the {@link PredicateSpecification}s to compose.
* @return the conjunction of the specifications.
@@ -123,7 +130,8 @@ public interface PredicateSpecification<T> extends Serializable {
}
/**
* Applies an AND operation to all the given {@link PredicateSpecification}s.
* Applies an AND operation to all the given {@link PredicateSpecification}s. If {@code specifications} is empty, the
* resulting {@link PredicateSpecification} will be unrestricted applying to all objects.
*
* @param specifications the {@link PredicateSpecification}s to compose.
* @return the conjunction of the specifications.
@@ -133,11 +141,12 @@ public interface PredicateSpecification<T> extends Serializable {
static <T> PredicateSpecification<T> allOf(Iterable<PredicateSpecification<T>> specifications) {
return StreamSupport.stream(specifications.spliterator(), false) //
.reduce(PredicateSpecification.all(), PredicateSpecification::and);
.reduce(PredicateSpecification.unrestricted(), PredicateSpecification::and);
}
/**
* Applies an OR operation to all the given {@link PredicateSpecification}s.
* Applies an OR operation to all the given {@link PredicateSpecification}s. If {@code specifications} is empty, the
* resulting {@link PredicateSpecification} will be unrestricted applying to all objects.
*
* @param specifications the {@link PredicateSpecification}s to compose.
* @return the disjunction of the specifications.
@@ -150,7 +159,8 @@ public interface PredicateSpecification<T> extends Serializable {
}
/**
* Applies an OR operation to all the given {@link PredicateSpecification}s.
* Applies an OR operation to all the given {@link PredicateSpecification}s. If {@code specifications} is empty, the
* resulting {@link PredicateSpecification} will be unrestricted applying to all objects.
*
* @param specifications the {@link PredicateSpecification}s to compose.
* @return the disjunction of the specifications.
@@ -160,7 +170,7 @@ public interface PredicateSpecification<T> extends Serializable {
static <T> PredicateSpecification<T> anyOf(Iterable<PredicateSpecification<T>> specifications) {
return StreamSupport.stream(specifications.spliterator(), false) //
.reduce(PredicateSpecification.all(), PredicateSpecification::or);
.reduce(PredicateSpecification.unrestricted(), PredicateSpecification::or);
}
/**

View File

@@ -32,6 +32,12 @@ import org.springframework.util.Assert;
/**
* Specification in the sense of Domain Driven Design.
* <p>
* Specifications can be composed into higher order functions from other specifications using
* {@link #and(Specification)}, {@link #or(Specification)} or factory methods such as {@link #allOf(Iterable)}.
* Composition considers whether one or more specifications contribute to the overall predicate by returning a
* {@link Predicate} or {@literal null}. Specifications returning {@literal null} are considered to not contribute to
* the overall predicate and their result is not considered in the final predicate.
*
* @author Oliver Gierke
* @author Thomas Darimont
@@ -51,7 +57,7 @@ public interface Specification<T> extends Serializable {
* @param <T> the type of the {@link Root} the resulting {@literal Specification} operates on.
* @return guaranteed to be not {@literal null}.
*/
static <T> Specification<T> all() {
static <T> Specification<T> unrestricted() {
return (root, query, builder) -> null;
}
@@ -148,13 +154,14 @@ public interface Specification<T> extends Serializable {
return (root, query, builder) -> {
Predicate not = spec.toPredicate(root, query, builder);
return not != null ? builder.not(not) : null;
Predicate predicate = spec.toPredicate(root, query, builder);
return predicate != null ? builder.not(predicate) : null;
};
}
/**
* Applies an AND operation to all the given {@link Specification}s.
* Applies an AND operation to all the given {@link Specification}s. If {@code specifications} is empty, the resulting
* {@link Specification} will be unrestricted applying to all objects.
*
* @param specifications the {@link Specification}s to compose.
* @return the conjunction of the specifications.
@@ -168,7 +175,8 @@ public interface Specification<T> extends Serializable {
}
/**
* Applies an AND operation to all the given {@link Specification}s.
* Applies an AND operation to all the given {@link Specification}s. If {@code specifications} is empty, the resulting
* {@link Specification} will be unrestricted applying to all objects.
*
* @param specifications the {@link Specification}s to compose.
* @return the conjunction of the specifications.
@@ -179,11 +187,12 @@ public interface Specification<T> extends Serializable {
static <T> Specification<T> allOf(Iterable<Specification<T>> specifications) {
return StreamSupport.stream(specifications.spliterator(), false) //
.reduce(Specification.all(), Specification::and);
.reduce(Specification.unrestricted(), Specification::and);
}
/**
* Applies an OR operation to all the given {@link Specification}s.
* Applies an OR operation to all the given {@link Specification}s. If {@code specifications} is empty, the resulting
* {@link Specification} will be unrestricted applying to all objects.
*
* @param specifications the {@link Specification}s to compose.
* @return the disjunction of the specifications
@@ -197,7 +206,8 @@ public interface Specification<T> extends Serializable {
}
/**
* Applies an OR operation to all the given {@link Specification}s.
* Applies an OR operation to all the given {@link Specification}s. If {@code specifications} is empty, the resulting
* {@link Specification} will be unrestricted applying to all objects.
*
* @param specifications the {@link Specification}s to compose.
* @return the disjunction of the specifications
@@ -208,7 +218,7 @@ public interface Specification<T> extends Serializable {
static <T> Specification<T> anyOf(Iterable<Specification<T>> specifications) {
return StreamSupport.stream(specifications.spliterator(), false) //
.reduce(Specification.all(), Specification::or);
.reduce(Specification.unrestricted(), Specification::or);
}
/**

View File

@@ -31,6 +31,12 @@ import org.springframework.util.Assert;
/**
* Specification in the sense of Domain Driven Design to handle Criteria Updates.
* <p>
* Specifications can be composed into higher order functions from other specifications using
* {@link #and(UpdateSpecification)}, {@link #or(UpdateSpecification)} or factory methods such as
* {@link #allOf(Iterable)}. Composition considers whether one or more specifications contribute to the overall
* predicate by returning a {@link Predicate} or {@literal null}. Specifications returning {@literal null} are
* considered to not contribute to the overall predicate and their result is not considered in the final predicate.
*
* @author Mark Paluch
* @since 4.0
@@ -39,27 +45,27 @@ import org.springframework.util.Assert;
public interface UpdateSpecification<T> extends Serializable {
/**
* Simple static factory method to create a specification deleting all objects.
* Simple static factory method to create a specification updating all objects.
*
* @param <T> the type of the {@link Root} the resulting {@literal UpdateSpecification} operates on.
* @return guaranteed to be not {@literal null}.
*/
static <T> UpdateSpecification<T> all() {
static <T> UpdateSpecification<T> unrestricted() {
return (root, query, builder) -> null;
}
/**
* Simple static factory method to add some syntactic sugar around a {@literal UpdateSpecification}. For example:
* Simple static factory method to add some syntactic sugar around a {@literal UpdateOperation}. For example:
*
* <pre class="code">
* UpdateSpecification&lt;User&gt; updateLastname = UpdateSpecification
* UpdateSpecification&lt;User&gt; updateLastname = UpdateOperation
* .&lt;User&gt; update((root, update, criteriaBuilder) -> update.set("lastname", "Heisenberg"))
* .where(userHasFirstname("Walter").and(userHasLastname("White")));
*
* repository.update(updateLastname);
* </pre>
*
* @param <T> the type of the {@link Root} the resulting {@literal UpdateSpecification} operates on.
* @param <T> the type of the {@link Root} the resulting {@literal UpdateOperation} operates on.
* @param spec must not be {@literal null}.
* @return guaranteed to be not {@literal null}.
*/
@@ -172,13 +178,14 @@ public interface UpdateSpecification<T> extends Serializable {
return (root, update, builder) -> {
Predicate not = spec.toPredicate(root, update, builder);
return not != null ? builder.not(not) : null;
Predicate predicate = spec.toPredicate(root, update, builder);
return predicate != null ? builder.not(predicate) : null;
};
}
/**
* Applies an AND operation to all the given {@link UpdateSpecification}s.
* Applies an AND operation to all the given {@link UpdateSpecification}s. If {@code specifications} is empty, the
* resulting {@link UpdateSpecification} will be unrestricted applying to all objects.
*
* @param specifications the {@link UpdateSpecification}s to compose.
* @return the conjunction of the specifications.
@@ -191,7 +198,8 @@ public interface UpdateSpecification<T> extends Serializable {
}
/**
* Applies an AND operation to all the given {@link UpdateSpecification}s.
* Applies an AND operation to all the given {@link UpdateSpecification}s. If {@code specifications} is empty, the
* resulting {@link UpdateSpecification} will be unrestricted applying to all objects.
*
* @param specifications the {@link UpdateSpecification}s to compose.
* @return the conjunction of the specifications.
@@ -201,11 +209,12 @@ public interface UpdateSpecification<T> extends Serializable {
static <T> UpdateSpecification<T> allOf(Iterable<UpdateSpecification<T>> specifications) {
return StreamSupport.stream(specifications.spliterator(), false) //
.reduce(UpdateSpecification.all(), UpdateSpecification::and);
.reduce(UpdateSpecification.unrestricted(), UpdateSpecification::and);
}
/**
* Applies an OR operation to all the given {@link UpdateSpecification}s.
* Applies an OR operation to all the given {@link UpdateSpecification}s. If {@code specifications} is empty, the
* resulting {@link UpdateSpecification} will be unrestricted applying to all objects.
*
* @param specifications the {@link UpdateSpecification}s to compose.
* @return the disjunction of the specifications.
@@ -218,7 +227,8 @@ public interface UpdateSpecification<T> extends Serializable {
}
/**
* Applies an OR operation to all the given {@link UpdateSpecification}s.
* Applies an OR operation to all the given {@link UpdateSpecification}s. If {@code specifications} is empty, the
* resulting {@link UpdateSpecification} will be unrestricted applying to all objects.
*
* @param specifications the {@link UpdateSpecification}s to compose.
* @return the disjunction of the specifications.
@@ -228,7 +238,7 @@ public interface UpdateSpecification<T> extends Serializable {
static <T> UpdateSpecification<T> anyOf(Iterable<UpdateSpecification<T>> specifications) {
return StreamSupport.stream(specifications.spliterator(), false) //
.reduce(UpdateSpecification.all(), UpdateSpecification::or);
.reduce(UpdateSpecification.unrestricted(), UpdateSpecification::or);
}
/**

View File

@@ -60,7 +60,7 @@ public interface JpaSpecificationExecutor<T> {
* @param spec must not be {@literal null}.
* @return never {@literal null}.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one entity found.
* @see Specification#all()
* @see Specification#unrestricted()
*/
default Optional<T> findOne(PredicateSpecification<T> spec) {
return findOne(Specification.where(spec));
@@ -72,7 +72,7 @@ public interface JpaSpecificationExecutor<T> {
* @param spec must not be {@literal null}.
* @return never {@literal null}.
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one entity found.
* @see Specification#all()
* @see Specification#unrestricted()
*/
Optional<T> findOne(Specification<T> spec);
@@ -81,7 +81,7 @@ public interface JpaSpecificationExecutor<T> {
*
* @param spec must not be {@literal null}.
* @return never {@literal null}.
* @see Specification#all()
* @see Specification#unrestricted()
*/
default List<T> findAll(PredicateSpecification<T> spec) {
return findAll(Specification.where(spec));
@@ -92,7 +92,7 @@ public interface JpaSpecificationExecutor<T> {
*
* @param spec must not be {@literal null}.
* @return never {@literal null}.
* @see Specification#all()
* @see Specification#unrestricted()
*/
List<T> findAll(Specification<T> spec);
@@ -102,7 +102,7 @@ public interface JpaSpecificationExecutor<T> {
* @param spec must not be {@literal null}.
* @param pageable must not be {@literal null}.
* @return never {@literal null}.
* @see Specification#all()
* @see Specification#unrestricted()
*/
Page<T> findAll(Specification<T> spec, Pageable pageable);
@@ -128,7 +128,7 @@ public interface JpaSpecificationExecutor<T> {
* @param spec must not be {@literal null}.
* @param sort must not be {@literal null}.
* @return never {@literal null}.
* @see Specification#all()
* @see Specification#unrestricted()
*/
List<T> findAll(Specification<T> spec, Sort sort);
@@ -137,7 +137,7 @@ public interface JpaSpecificationExecutor<T> {
*
* @param spec the {@link PredicateSpecification} to count instances for, must not be {@literal null}.
* @return the number of instances.
* @see Specification#all()
* @see Specification#unrestricted()
*/
default long count(PredicateSpecification<T> spec) {
return count(Specification.where(spec));
@@ -148,7 +148,7 @@ public interface JpaSpecificationExecutor<T> {
*
* @param spec the {@link Specification} to count instances for, must not be {@literal null}.
* @return the number of instances.
* @see Specification#all()
* @see Specification#unrestricted()
*/
long count(Specification<T> spec);
@@ -158,7 +158,7 @@ public interface JpaSpecificationExecutor<T> {
* @param spec the {@link PredicateSpecification} to use for the existence check, must not be {@literal null}.
* @return {@code true} if the data store contains elements that match the given {@link PredicateSpecification}
* otherwise {@code false}.
* @see Specification#all()
* @see Specification#unrestricted()
*/
default boolean exists(PredicateSpecification<T> spec) {
return exists(Specification.where(spec));
@@ -170,7 +170,7 @@ public interface JpaSpecificationExecutor<T> {
* @param spec the {@link Specification} to use for the existence check, must not be {@literal null}.
* @return {@code true} if the data store contains elements that match the given {@link Specification} otherwise
* {@code false}.
* @see Specification#all()
* @see Specification#unrestricted()
*/
boolean exists(Specification<T> spec);
@@ -195,7 +195,7 @@ public interface JpaSpecificationExecutor<T> {
* @param spec the {@link PredicateSpecification} to use for the delete query, must not be {@literal null}.
* @return the number of entities deleted.
* @since 3.0
* @see PredicateSpecification#all()
* @see PredicateSpecification#unrestricted()
*/
default long delete(PredicateSpecification<T> spec) {
return delete(DeleteSpecification.where(spec));
@@ -210,7 +210,7 @@ public interface JpaSpecificationExecutor<T> {
* @param spec the {@link UpdateSpecification} to use for the delete query must not be {@literal null}.
* @return the number of entities deleted.
* @since 3.0
* @see DeleteSpecification#all()
* @see DeleteSpecification#unrestricted()
*/
long delete(DeleteSpecification<T> spec);

View File

@@ -401,7 +401,7 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
@Override
public List<T> findAll() {
return getQuery(Specification.all(), Sort.unsorted()).getResultList();
return getQuery(Specification.unrestricted(), Sort.unsorted()).getResultList();
}
@Override
@@ -434,12 +434,12 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
@Override
public List<T> findAll(Sort sort) {
return getQuery(Specification.all(), sort).getResultList();
return getQuery(Specification.unrestricted(), sort).getResultList();
}
@Override
public Page<T> findAll(Pageable pageable) {
return findAll(Specification.all(), pageable);
return findAll(Specification.unrestricted(), pageable);
}
@Override
@@ -1094,7 +1094,7 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
@SuppressWarnings("rawtypes")
private static final class ByIdsSpecification<T> implements Specification<T> {
@Serial private static final @Serial long serialVersionUID = 1L;
private static final @Serial long serialVersionUID = 1L;
private final JpaEntityInformation<T, ?> entityInformation;
@@ -1124,7 +1124,7 @@ public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T
*/
private static class ExampleSpecification<T> implements Specification<T> {
@Serial private static final @Serial long serialVersionUID = 1L;
private static final @Serial long serialVersionUID = 1L;
private final Example<T> example;
private final EscapeCharacter escapeCharacter;

View File

@@ -59,7 +59,7 @@ class DeleteSpecificationUnitTests implements Serializable {
@Test // GH-3521
void allReturnsEmptyPredicate() {
DeleteSpecification<Object> specification = DeleteSpecification.all();
DeleteSpecification<Object> specification = DeleteSpecification.unrestricted();
assertThat(specification).isNotNull();
assertThat(specification.toPredicate(root, delete, builder)).isNull();

View File

@@ -57,7 +57,7 @@ class PredicateSpecificationUnitTests implements Serializable {
@Test // GH-3521
void allReturnsEmptyPredicate() {
PredicateSpecification<Object> specification = PredicateSpecification.all();
PredicateSpecification<Object> specification = PredicateSpecification.unrestricted();
assertThat(specification).isNotNull();
assertThat(specification.toPredicate(root, builder)).isNull();

View File

@@ -59,7 +59,7 @@ class UpdateSpecificationUnitTests implements Serializable {
@Test // GH-3521
void allReturnsEmptyPredicate() {
UpdateSpecification<Object> specification = UpdateSpecification.all();
UpdateSpecification<Object> specification = UpdateSpecification.unrestricted();
assertThat(specification).isNotNull();
assertThat(specification.toPredicate(root, update, builder)).isNull();

View File

@@ -593,7 +593,7 @@ class UserRepositoryTests {
void returnsSameListIfNoSpecGiven() {
flushTestUsers();
assertSameElements(repository.findAll(), repository.findAll(PredicateSpecification.all()));
assertSameElements(repository.findAll(), repository.findAll(PredicateSpecification.unrestricted()));
}
@Test
@@ -609,7 +609,7 @@ class UserRepositoryTests {
Pageable pageable = PageRequest.of(0, 1);
flushTestUsers();
assertThat(repository.findAll(Specification.all(), pageable)).isEqualTo(repository.findAll(pageable));
assertThat(repository.findAll(Specification.unrestricted(), pageable)).isEqualTo(repository.findAll(pageable));
}
@Test // GH-3521
@@ -633,7 +633,7 @@ class UserRepositoryTests {
flushTestUsers();
repository.delete(DeleteSpecification.all());
repository.delete(DeleteSpecification.unrestricted());
assertThat(repository.count()).isEqualTo(0L);
}
@@ -643,7 +643,7 @@ class UserRepositoryTests {
flushTestUsers();
repository.delete(DeleteSpecification.all());
repository.delete(DeleteSpecification.unrestricted());
assertThat(repository.count()).isEqualTo(0L);
}

View File

@@ -219,7 +219,7 @@ class SimpleJpaRepositoryUnitTests {
when(query.getResultList()).thenReturn(Arrays.asList(new User(), new User()));
repo.findAll(Specification.all(), PageRequest.of(2, 1));
repo.findAll(Specification.unrestricted(), PageRequest.of(2, 1));
verify(metadata).getQueryHintsForCount();
}