From 7b3dd770484770c7b37fdf7b90ede20f347ac046 Mon Sep 17 00:00:00 2001 From: Diego Krupitza Date: Mon, 21 Feb 2022 20:36:34 +0100 Subject: [PATCH] Added exists method with specification to `JpaSpecificationExecutor`. It is now possible to make existence checks based on a `Specification`. This is an addition to checking existence with using an `Example`. Closes #2388 Original pull request #2449 --- .../repository/JpaSpecificationExecutor.java | 10 +++++++++ .../support/SimpleJpaRepository.java | 21 ++++++++++++++----- .../jpa/domain/sample/UserSpecifications.java | 12 +++++++++++ .../jpa/repository/UserRepositoryTests.java | 12 +++++++++++ 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java index 63c83f996..e310f8709 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java @@ -29,6 +29,7 @@ import org.springframework.lang.Nullable; * * @author Oliver Gierke * @author Christoph Strobl + * @author Diego Krupitza */ public interface JpaSpecificationExecutor { @@ -74,4 +75,13 @@ public interface JpaSpecificationExecutor { * @return the number of instances. */ long count(@Nullable Specification spec); + + /** + * Checks whether the data store contains elements that match the given {@link Specification}. + * + * @param spec the {@link Specification} to use for the existence check. Must not be {@literal null}. + * @return true if the data store contains elements that match the given {@link Specification} otherwise + * false. + */ + boolean exists(Specification spec); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java index 29b8d5e46..e24613e6f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java @@ -40,11 +40,7 @@ import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Root; import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.data.domain.Example; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; +import org.springframework.data.domain.*; import org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.provider.PersistenceProvider; @@ -81,6 +77,7 @@ import org.springframework.util.Assert; * @author Greg Turnquist * @author Yanming Zhou * @author Ernst-Jan van der Laan + * @author Diego Krupitza */ @Repository @Transactional(readOnly = true) @@ -464,6 +461,20 @@ public class SimpleJpaRepository implements JpaRepositoryImplementation spec) { + + CriteriaQuery cq = this.em.getCriteriaBuilder().createQuery(Integer.class); + cq.select(this.em.getCriteriaBuilder().literal(1)); + applySpecificationToCriteria(spec, getDomainClass(), cq); + TypedQuery query = applyRepositoryMethodMetadata(this.em.createQuery(cq)); + return query.setMaxResults(1).getResultList().size() == 1; + } + @Override public List findAll(Example example) { return getQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType(), Sort.unsorted()) diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserSpecifications.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserSpecifications.java index 5ebd6a453..e63e0f850 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserSpecifications.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserSpecifications.java @@ -26,6 +26,7 @@ import org.springframework.data.jpa.domain.Specification; * Collection of {@link Specification}s for a {@link User}. * * @author Oliver Gierke + * @author Diego Krupitza */ public class UserSpecifications { @@ -69,6 +70,17 @@ public class UserSpecifications { }; } + /** + * A {@link Specification} to do an age check. + * + * @param age upper (exclusive) bound of the age + * @return + */ + public static Specification userHasAgeLess(final Integer age) { + + return (root, query, cb) -> cb.lessThan(root.get("age").as(Integer.class), age); + } + /** * A {@link Specification} to do a like-match on a {@link User}'s lastname but also adding a sort order on the * firstname. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index aa411599e..bd8664520 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -96,6 +96,7 @@ import org.springframework.transaction.annotation.Transactional; * @author Sander Krabbenborg * @author Jesse Wouters * @author Greg Turnquist + * @author Diego Krupitza */ @ExtendWith(SpringExtension.class) @ContextConfiguration("classpath:application-context.xml") @@ -2640,6 +2641,17 @@ public class UserRepositoryTests { assertThat(repository.findAllInterfaceProjectedBy()).hasSize(4); } + @Test // GH-2388 + void existsWithSpec() { + flushTestUsers(); + + Specification minorSpec = userHasAgeLess(18); + Specification hundredYearsOld = userHasAgeLess(100); + + assertThat(repository.exists(minorSpec)).isFalse(); + assertThat(repository.exists(hundredYearsOld)).isTrue(); + } + private Page executeSpecWithSort(Sort sort) { flushTestUsers();