From e0dff8ebaa44da23fb0da3c0d8ca8c204bf5f2dc 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 with `Specification`s. This is an addition to checking existence with using an `Example`. Closes #2388 --- .../repository/JpaSpecificationExecutor.java | 10 ++++ .../support/SimpleJpaRepository.java | 52 +++++++++---------- .../jpa/domain/sample/UserSpecifications.java | 12 +++++ .../jpa/repository/UserRepositoryTests.java | 12 +++++ 4 files changed, 58 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java b/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java index 63c83f9963..e310f8709f 100644 --- a/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java +++ b/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java @@ -29,6 +29,7 @@ * * @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/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java index 4b75ff7679..dcc493c721 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java @@ -15,12 +15,16 @@ */ package org.springframework.data.jpa.repository.support; +import static org.springframework.data.jpa.repository.query.QueryUtils.*; + +import java.util.*; +import java.util.function.Function; + +import javax.persistence.*; +import javax.persistence.criteria.*; + 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; @@ -37,29 +41,6 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; -import javax.persistence.EntityManager; -import javax.persistence.LockModeType; -import javax.persistence.NoResultException; -import javax.persistence.Parameter; -import javax.persistence.Query; -import javax.persistence.TypedQuery; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.ParameterExpression; -import javax.persistence.criteria.Path; -import javax.persistence.criteria.Predicate; -import javax.persistence.criteria.Root; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.Function; - -import static org.springframework.data.jpa.repository.query.QueryUtils.*; - /** * Default implementation of the {@link org.springframework.data.repository.CrudRepository} interface. This will offer * you a more sophisticated interface than the plain {@link EntityManager} . @@ -80,6 +61,7 @@ * @author Greg Turnquist * @author Yanming Zhou * @author Ernst-Jan van der Laan + * @author Diego Krupitza */ @Repository @Transactional(readOnly = true) @@ -555,6 +537,20 @@ public boolean exists(Example example) { return query.setMaxResults(1).getResultList().size() == 1; } + /* + * (non-Javadoc) + * @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#exists(org.springframework.data.jpa.domain.Specification) + */ + @Override + public boolean exists(Specification 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; + } + /* * (non-Javadoc) * @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example) diff --git a/src/test/java/org/springframework/data/jpa/domain/sample/UserSpecifications.java b/src/test/java/org/springframework/data/jpa/domain/sample/UserSpecifications.java index d64ffdee96..de8b99b88a 100644 --- a/src/test/java/org/springframework/data/jpa/domain/sample/UserSpecifications.java +++ b/src/test/java/org/springframework/data/jpa/domain/sample/UserSpecifications.java @@ -26,6 +26,7 @@ * Collection of {@link Specification}s for a {@link User}. * * @author Oliver Gierke + * @author Diego Krupitza */ public class UserSpecifications { @@ -69,6 +70,17 @@ public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBu }; } + /** + * 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/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index d90ff71ca8..68ccc94795 100644 --- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -96,6 +96,7 @@ * @author Sander Krabbenborg * @author Jesse Wouters * @author Greg Turnquist + * @author Diego Krupitza */ @ExtendWith(SpringExtension.class) @ContextConfiguration("classpath:application-context.xml") @@ -2640,6 +2641,17 @@ void readsDerivedInterfaceProjections() { 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();