diff --git a/src/main/java/org/springframework/data/jpa/domain/Specification.java b/src/main/java/org/springframework/data/jpa/domain/Specification.java index d7c87573d..91db40f37 100644 --- a/src/main/java/org/springframework/data/jpa/domain/Specification.java +++ b/src/main/java/org/springframework/data/jpa/domain/Specification.java @@ -16,7 +16,6 @@ package org.springframework.data.jpa.domain; import static org.springframework.data.jpa.domain.SpecificationComposition.*; -import static org.springframework.data.jpa.domain.SpecificationComposition.CompositionType.*; import java.io.Serializable; @@ -40,8 +39,6 @@ public interface Specification extends Serializable { long serialVersionUID = 1L; - Specification EMPTY_SPEC = (root, query, criteriaBuilder) -> null; - /** * Negates the given {@link Specification}. * @@ -50,8 +47,11 @@ public interface Specification extends Serializable { * @return * @since 2.0 */ - static Specification not(Specification spec) { - return negated(spec); + static Specification not(@Nullable Specification spec) { + + return spec == null // + ? (root, query, builder) -> null// + : (root, query, builder) -> builder.not(spec.toPredicate(root, query, builder)); } /** @@ -62,8 +62,9 @@ public interface Specification extends Serializable { * @return * @since 2.0 */ - static Specification where(Specification spec) { - return spec == null ? EMPTY_SPEC : spec; + @Nullable + static Specification where(@Nullable Specification spec) { + return spec == null ? (root, query, builder) -> null : spec; } /** @@ -73,8 +74,9 @@ public interface Specification extends Serializable { * @return The conjunction of the specifications * @since 2.0 */ - default Specification and(Specification other) { - return composed(this, other, AND); + @Nullable + default Specification and(@Nullable Specification other) { + return composed(this, other, (builder, left, rhs) -> builder.and(left, rhs)); } /** @@ -84,8 +86,9 @@ public interface Specification extends Serializable { * @return The disjunction of the specifications * @since 2.0 */ - default Specification or(Specification other) { - return composed(this, other, OR); + @Nullable + default Specification or(@Nullable Specification other) { + return composed(this, other, (builder, left, rhs) -> builder.or(left, rhs)); } /** diff --git a/src/main/java/org/springframework/data/jpa/domain/SpecificationComposition.java b/src/main/java/org/springframework/data/jpa/domain/SpecificationComposition.java index e8cc266f4..e4e073868 100644 --- a/src/main/java/org/springframework/data/jpa/domain/SpecificationComposition.java +++ b/src/main/java/org/springframework/data/jpa/domain/SpecificationComposition.java @@ -1,56 +1,62 @@ +/* + * Copyright 2018 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.springframework.data.jpa.domain; +import java.io.Serializable; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + import org.springframework.lang.Nullable; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.Predicate; - /** - * Helper class to support specification compositions + * Helper class to support specification compositions. * * @author Sebastian Staudt + * @author Oliver Gierke * @see Specification + * @since 2.2 */ class SpecificationComposition { - /** - * Enum for the composition types for {@link Predicate}s. Can not be turned into lambdas as we need to be - * serializable. - * - * @author Thomas Darimont - */ - enum CompositionType { + interface Combiner extends Serializable { + Predicate combine(CriteriaBuilder builder, @Nullable Predicate lhs, @Nullable Predicate rhs); + } - AND { - @Override - public Predicate combine(CriteriaBuilder builder, Predicate lhs, Predicate rhs) { - return builder.and(lhs, rhs); - } - }, + @Nullable + static Specification composed(@Nullable Specification lhs, @Nullable Specification rhs, + Combiner combiner) { - OR { - @Override - public Predicate combine(CriteriaBuilder builder, Predicate lhs, Predicate rhs) { - return builder.or(lhs, rhs); - } - }; + return (root, query, builder) -> { - abstract Predicate combine(CriteriaBuilder builder, Predicate lhs, Predicate rhs); - } + Predicate otherPredicate = toPredicate(lhs, root, query, builder); + Predicate thisPredicate = toPredicate(rhs, root, query, builder); - static Specification negated(@Nullable Specification spec) { - return (root, query, builder) -> spec == null ? null : builder.not(spec.toPredicate(root, query, builder)); - } + if (thisPredicate == null) { + return otherPredicate; + } - static Specification composed(@Nullable Specification lhs, @Nullable Specification rhs, CompositionType compositionType) { + return otherPredicate == null ? thisPredicate : combiner.combine(builder, thisPredicate, otherPredicate); + }; + } - return (root, query, builder) -> { - - Predicate otherPredicate = rhs == null ? null : rhs.toPredicate(root, query, builder); - Predicate thisPredicate = lhs == null ? null : lhs.toPredicate(root, query, builder); - - return thisPredicate == null ? otherPredicate - : otherPredicate == null ? thisPredicate : compositionType.combine(builder, thisPredicate, otherPredicate); - }; - } + private static Predicate toPredicate(Specification specification, Root root, CriteriaQuery query, + CriteriaBuilder builder) { + return specification == null ? null : specification.toPredicate(root, query, builder); + } }