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 63c83f996..e310f8709 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 @@ 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/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java index 4b75ff767..dcc493c72 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.stereotype.Repository; 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 @@ import static org.springframework.data.jpa.repository.query.QueryUtils.*; * @author Greg Turnquist * @author Yanming Zhou * @author Ernst-Jan van der Laan + * @author Diego Krupitza */ @Repository @Transactional(readOnly = true) @@ -555,6 +537,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; + } + /* * (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 d64ffdee9..de8b99b88 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 @@ 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/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index d90ff71ca..68ccc9479 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 @@ 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();