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
This commit is contained in:
Diego Krupitza
2022-02-21 20:36:34 +01:00
committed by Jens Schauder
parent 8fb25c6b45
commit 36c9b4d660
4 changed files with 58 additions and 28 deletions

View File

@@ -29,6 +29,7 @@ import org.springframework.lang.Nullable;
*
* @author Oliver Gierke
* @author Christoph Strobl
* @author Diego Krupitza
*/
public interface JpaSpecificationExecutor<T> {
@@ -74,4 +75,13 @@ public interface JpaSpecificationExecutor<T> {
* @return the number of instances.
*/
long count(@Nullable Specification<T> 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 <code>true</code> if the data store contains elements that match the given {@link Specification} otherwise
* <code>false</code>.
*/
boolean exists(Specification<T> spec);
}

View File

@@ -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<T, ID> implements JpaRepositoryImplementation<T
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<T> spec) {
CriteriaQuery<Integer> cq = this.em.getCriteriaBuilder().createQuery(Integer.class);
cq.select(this.em.getCriteriaBuilder().literal(1));
applySpecificationToCriteria(spec, getDomainClass(), cq);
TypedQuery<Integer> 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)

View File

@@ -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<User> 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.

View File

@@ -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<User> minorSpec = userHasAgeLess(18);
Specification<User> hundredYearsOld = userHasAgeLess(100);
assertThat(repository.exists(minorSpec)).isFalse();
assertThat(repository.exists(hundredYearsOld)).isTrue();
}
private Page<User> executeSpecWithSort(Sort sort) {
flushTestUsers();