diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/DeleteSpecification.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/DeleteSpecification.java
index 4c7deb638d..79a3865afe 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/DeleteSpecification.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/DeleteSpecification.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 the original author or authors.
+ * Copyright 2024-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,18 +35,30 @@
*
* 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.
+ * {@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}, such as {@link #unrestricted()}, are
+ * considered to not contribute to the overall predicate, and their result is not considered in the final predicate.
*
* @author Mark Paluch
+ * @author Peter Aisher
* @since 4.0
*/
@FunctionalInterface
public interface DeleteSpecification extends Serializable {
/**
- * Simple static factory method to create a specification deleting all objects.
+ * Simple static factory method to create a specification which does not participate in matching. The specification
+ * returned is {@code null}-like, and is elided in all operations.
+ *
+ *
+ * {@code
+ * unrestricted().and(other) // consider only `other`
+ * unrestricted().or(other) // consider only `other`
+ * not(unrestricted()) // equivalent to `unrestricted()`
+ * }
+ *
*
* @param the type of the {@link Root} the resulting {@literal DeleteSpecification} operates on.
* @return guaranteed to be not {@literal null}.
@@ -159,7 +171,7 @@ static DeleteSpecification not(DeleteSpecification spec) {
return (root, delete, builder) -> {
Predicate predicate = spec.toPredicate(root, delete, builder);
- return predicate != null ? builder.not(predicate) : builder.disjunction();
+ return predicate != null ? builder.not(predicate) : null;
};
}
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/PredicateSpecification.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/PredicateSpecification.java
index daa39b9ba7..3c7e048b81 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/PredicateSpecification.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/PredicateSpecification.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 the original author or authors.
+ * Copyright 2024-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,18 +34,30 @@
*
* 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.
+ * {@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}, such as {@link #unrestricted()}, are
+ * considered to not contribute to the overall predicate, and their result is not considered in the final predicate.
*
* @author Mark Paluch
+ * @author Peter Aisher
* @since 4.0
*/
@FunctionalInterface
public interface PredicateSpecification extends Serializable {
/**
- * Simple static factory method to create a specification matching all objects.
+ * Simple static factory method to create a specification which does not participate in matching. The specification
+ * returned is {@code null}-like, and is elided in all operations.
+ *
+ *
+ * {@code
+ * unrestricted().and(other) // consider only `other`
+ * unrestricted().or(other) // consider only `other`
+ * not(unrestricted()) // equivalent to `unrestricted()`
+ * }
+ *
*
* @param the type of the {@link Root} the resulting {@literal PredicateSpecification} operates on.
* @return guaranteed to be not {@literal null}.
@@ -113,7 +125,7 @@ static PredicateSpecification not(PredicateSpecification spec) {
return (root, builder) -> {
Predicate predicate = spec.toPredicate(root, builder);
- return predicate != null ? builder.not(predicate) : builder.disjunction();
+ return predicate != null ? builder.not(predicate) : null;
};
}
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/Specification.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/Specification.java
index 8427c5e8aa..b0b2c943b2 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/Specification.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/Specification.java
@@ -36,9 +36,10 @@
*
* 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.
+ * {@link Predicate} or {@literal null}. Specifications returning {@literal null}, such as {@link #unrestricted()}, 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
@@ -49,12 +50,22 @@
* @author Daniel Shuy
* @author Sergey Rukin
* @author Heeeun Cho
+ * @author Peter Aisher
*/
@FunctionalInterface
public interface Specification extends Serializable {
/**
- * Simple static factory method to create a specification matching all objects.
+ * Simple static factory method to create a specification which does not participate in matching. The specification
+ * returned is {@code null}-like, and is elided in all operations.
+ *
+ *
+ * {@code
+ * unrestricted().and(other) // consider only `other`
+ * unrestricted().or(other) // consider only `other`
+ * not(unrestricted()) // equivalent to `unrestricted()`
+ * }
+ *
*
* @param the type of the {@link Root} the resulting {@literal Specification} operates on.
* @return guaranteed to be not {@literal null}.
@@ -175,7 +186,7 @@ static Specification not(Specification spec) {
return (root, query, builder) -> {
Predicate predicate = spec.toPredicate(root, query, builder);
- return predicate != null ? builder.not(predicate) : builder.disjunction();
+ return predicate != null ? builder.not(predicate) : null;
};
}
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/UpdateSpecification.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/UpdateSpecification.java
index 1a27d428a4..02bff0c3d6 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/UpdateSpecification.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/UpdateSpecification.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 the original author or authors.
+ * Copyright 2024-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,18 +35,30 @@
*
* 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.
+ * {@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}, such as {@link #unrestricted()}, are
+ * considered to not contribute to the overall predicate, and their result is not considered in the final predicate.
*
* @author Mark Paluch
+ * @author Peter Aisher
* @since 4.0
*/
@FunctionalInterface
public interface UpdateSpecification extends Serializable {
/**
- * Simple static factory method to create a specification updating all objects.
+ * Simple static factory method to create a specification which does not participate in matching. The specification
+ * returned is {@code null}-like, and is elided in all operations.
+ *
+ *
+ * {@code
+ * unrestricted().and(other) // consider only `other`
+ * unrestricted().or(other) // consider only `other`
+ * not(unrestricted()) // equivalent to `unrestricted()`
+ * }
+ *
*
* @param the type of the {@link Root} the resulting {@literal UpdateSpecification} operates on.
* @return guaranteed to be not {@literal null}.
@@ -180,7 +192,7 @@ static UpdateSpecification not(UpdateSpecification spec) {
return (root, update, builder) -> {
Predicate predicate = spec.toPredicate(root, update, builder);
- return predicate != null ? builder.not(predicate) : builder.disjunction();
+ return predicate != null ? builder.not(predicate) : null;
};
}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/DeleteSpecificationUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/DeleteSpecificationUnitTests.java
index 99e6bb80ac..13e051c46f 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/DeleteSpecificationUnitTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/DeleteSpecificationUnitTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 the original author or authors.
+ * Copyright 2024-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,6 +38,7 @@
* Unit tests for {@link DeleteSpecification}.
*
* @author Mark Paluch
+ * @author Peter Aisher
*/
@SuppressWarnings({ "unchecked", "deprecation" })
@ExtendWith(MockitoExtension.class)
@@ -158,15 +159,13 @@ void orCombinesSpecificationsInOrder() {
verify(builder).or(firstPredicate, secondPredicate);
}
- @Test // GH-3849
+ @Test // GH-3849, GH-4023
void notWithNullPredicate() {
- when(builder.disjunction()).thenReturn(mock(Predicate.class));
-
DeleteSpecification