Skip to content

Commit 7b3dd77

Browse files
DiegoKrupitzaschauder
authored andcommitted
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
1 parent 57b9b21 commit 7b3dd77

File tree

4 files changed

+50
-5
lines changed

4 files changed

+50
-5
lines changed

Diff for: spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java

+10
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
*
3030
* @author Oliver Gierke
3131
* @author Christoph Strobl
32+
* @author Diego Krupitza
3233
*/
3334
public interface JpaSpecificationExecutor<T> {
3435

@@ -74,4 +75,13 @@ public interface JpaSpecificationExecutor<T> {
7475
* @return the number of instances.
7576
*/
7677
long count(@Nullable Specification<T> spec);
78+
79+
/**
80+
* Checks whether the data store contains elements that match the given {@link Specification}.
81+
*
82+
* @param spec the {@link Specification} to use for the existence check. Must not be {@literal null}.
83+
* @return <code>true</code> if the data store contains elements that match the given {@link Specification} otherwise
84+
* <code>false</code>.
85+
*/
86+
boolean exists(Specification<T> spec);
7787
}

Diff for: spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java

+16-5
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,7 @@
4040
import jakarta.persistence.criteria.Root;
4141

4242
import org.springframework.dao.EmptyResultDataAccessException;
43-
import org.springframework.data.domain.Example;
44-
import org.springframework.data.domain.Page;
45-
import org.springframework.data.domain.PageImpl;
46-
import org.springframework.data.domain.Pageable;
47-
import org.springframework.data.domain.Sort;
43+
import org.springframework.data.domain.*;
4844
import org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder;
4945
import org.springframework.data.jpa.domain.Specification;
5046
import org.springframework.data.jpa.provider.PersistenceProvider;
@@ -81,6 +77,7 @@
8177
* @author Greg Turnquist
8278
* @author Yanming Zhou
8379
* @author Ernst-Jan van der Laan
80+
* @author Diego Krupitza
8481
*/
8582
@Repository
8683
@Transactional(readOnly = true)
@@ -464,6 +461,20 @@ public <S extends T> boolean exists(Example<S> example) {
464461
return query.setMaxResults(1).getResultList().size() == 1;
465462
}
466463

464+
/*
465+
* (non-Javadoc)
466+
* @see org.springframework.data.jpa.repository.JpaSpecificationExecutor#exists(org.springframework.data.jpa.domain.Specification)
467+
*/
468+
@Override
469+
public boolean exists(Specification<T> spec) {
470+
471+
CriteriaQuery<Integer> cq = this.em.getCriteriaBuilder().createQuery(Integer.class);
472+
cq.select(this.em.getCriteriaBuilder().literal(1));
473+
applySpecificationToCriteria(spec, getDomainClass(), cq);
474+
TypedQuery<Integer> query = applyRepositoryMethodMetadata(this.em.createQuery(cq));
475+
return query.setMaxResults(1).getResultList().size() == 1;
476+
}
477+
467478
@Override
468479
public <S extends T> List<S> findAll(Example<S> example) {
469480
return getQuery(new ExampleSpecification<>(example, escapeCharacter), example.getProbeType(), Sort.unsorted())

Diff for: spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserSpecifications.java

+12
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
* Collection of {@link Specification}s for a {@link User}.
2727
*
2828
* @author Oliver Gierke
29+
* @author Diego Krupitza
2930
*/
3031
public class UserSpecifications {
3132

@@ -69,6 +70,17 @@ public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBu
6970
};
7071
}
7172

73+
/**
74+
* A {@link Specification} to do an age check.
75+
*
76+
* @param age upper (exclusive) bound of the age
77+
* @return
78+
*/
79+
public static Specification<User> userHasAgeLess(final Integer age) {
80+
81+
return (root, query, cb) -> cb.lessThan(root.get("age").as(Integer.class), age);
82+
}
83+
7284
/**
7385
* A {@link Specification} to do a like-match on a {@link User}'s lastname but also adding a sort order on the
7486
* firstname.

Diff for: spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java

+12
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
* @author Sander Krabbenborg
9797
* @author Jesse Wouters
9898
* @author Greg Turnquist
99+
* @author Diego Krupitza
99100
*/
100101
@ExtendWith(SpringExtension.class)
101102
@ContextConfiguration("classpath:application-context.xml")
@@ -2640,6 +2641,17 @@ void readsDerivedInterfaceProjections() {
26402641
assertThat(repository.findAllInterfaceProjectedBy()).hasSize(4);
26412642
}
26422643

2644+
@Test // GH-2388
2645+
void existsWithSpec() {
2646+
flushTestUsers();
2647+
2648+
Specification<User> minorSpec = userHasAgeLess(18);
2649+
Specification<User> hundredYearsOld = userHasAgeLess(100);
2650+
2651+
assertThat(repository.exists(minorSpec)).isFalse();
2652+
assertThat(repository.exists(hundredYearsOld)).isTrue();
2653+
}
2654+
26432655
private Page<User> executeSpecWithSort(Sort sort) {
26442656

26452657
flushTestUsers();

0 commit comments

Comments
 (0)