From ae0ea2ec7c8114c7c4daa2d80a4ea485d12538ba Mon Sep 17 00:00:00 2001 From: Giovanni Lovato Date: Wed, 1 Aug 2018 18:52:19 +0200 Subject: [PATCH] [#564] Add support for specifications --- ...AbstractPartTreeBlazePersistenceQuery.java | 42 +++++++++++++++++-- .../spring/data/base/query/JpaParameters.java | 35 +++++++++++++++- .../testsuite/DocumentRepositoryTest.java | 23 ++++++++++ .../repository/DocumentRepository.java | 3 ++ 4 files changed, 98 insertions(+), 5 deletions(-) diff --git a/integration/spring-data/base/src/main/java/com/blazebit/persistence/spring/data/base/query/AbstractPartTreeBlazePersistenceQuery.java b/integration/spring-data/base/src/main/java/com/blazebit/persistence/spring/data/base/query/AbstractPartTreeBlazePersistenceQuery.java index 2ae73e3609..82992b77d0 100644 --- a/integration/spring-data/base/src/main/java/com/blazebit/persistence/spring/data/base/query/AbstractPartTreeBlazePersistenceQuery.java +++ b/integration/spring-data/base/src/main/java/com/blazebit/persistence/spring/data/base/query/AbstractPartTreeBlazePersistenceQuery.java @@ -21,6 +21,7 @@ import com.blazebit.persistence.PagedList; import com.blazebit.persistence.PaginatedCriteriaBuilder; import com.blazebit.persistence.criteria.BlazeCriteria; +import com.blazebit.persistence.criteria.BlazeCriteriaBuilder; import com.blazebit.persistence.criteria.BlazeCriteriaQuery; import com.blazebit.persistence.spring.data.base.query.JpaParameters.JpaParameter; import com.blazebit.persistence.view.EntityViewManager; @@ -28,6 +29,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.jpa.repository.query.AbstractJpaQuery; import org.springframework.data.jpa.repository.query.FixedJpaCountQueryCreator; @@ -44,8 +46,12 @@ import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + import java.util.Collections; import java.util.List; +import java.util.regex.Pattern; /** * Implementation is similar to {@link PartTreeJpaQuery} but was modified to work with entity views. @@ -56,6 +62,8 @@ */ public abstract class AbstractPartTreeBlazePersistenceQuery extends AbstractJpaQuery { + private static final Pattern QUERY_PATTERN = Pattern.compile("^(find|read|get|query|stream)All$"); + private final Class domainClass; private final Class entityViewClass; private final PartTree tree; @@ -73,12 +81,20 @@ public AbstractPartTreeBlazePersistenceQuery(EntityViewAwareJpaQueryMethod metho this.evm = evm; this.entityViewClass = method.getEntityViewClass(); - this.domainClass = method.getEntityInformation().getJavaType(); - this.tree = new PartTree(method.getName(), domainClass); - this.parameters = method.getJpaParameters(); - boolean recreateQueries = parameters.potentiallySortsDynamically() || entityViewClass != null; + this.parameters = method.getJpaParameters(); + String methodName = method.getName(); + boolean matchesSpecificationSignature = parameters.hasSpecificationParameter() + && QUERY_PATTERN.matcher(methodName).matches(); + String source = matchesSpecificationSignature ? "" : methodName; + this.tree = new PartTree(source, domainClass); + + /*- + boolean recreateQueries = parameters.potentiallySortsDynamically() || entityViewClass != null + || matchesSpecificationSignature; + -*/ + boolean recreateQueries = true; this.query = isCountProjection(tree) ? new AbstractPartTreeBlazePersistenceQuery.CountQueryPreparer(persistenceProvider, recreateQueries) : new AbstractPartTreeBlazePersistenceQuery.QueryPreparer(persistenceProvider, recreateQueries); } @@ -238,7 +254,10 @@ private TypedQuery createQuery(CriteriaQuery criteriaQuery, Object[] value } protected TypedQuery createQuery0(CriteriaQuery criteriaQuery, Object[] values) { + processSpecification(criteriaQuery, values); + com.blazebit.persistence.CriteriaBuilder cb = ((BlazeCriteriaQuery) criteriaQuery).createCriteriaBuilder(); + if (entityViewClass == null) { return cb.getQuery(); } else { @@ -259,6 +278,8 @@ Query createPaginatedQuery(Object[] values, boolean withCount) { expressions = creator.getParameterExpressions(); } + processSpecification(criteriaQuery, values); + com.blazebit.persistence.CriteriaBuilder cb = ((BlazeCriteriaQuery) criteriaQuery).createCriteriaBuilder(); TypedQuery jpaQuery; ParameterBinder binder = getBinder(values, expressions); @@ -294,6 +315,19 @@ protected void processSetting(EntityViewSetting setting, Object[] values) } } + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected void processSpecification(CriteriaQuery criteriaQuery, Object[] values) { + BlazeCriteriaQuery blazeCriteriaQuery = (BlazeCriteriaQuery) criteriaQuery; + int specificationIndex = parameters.getSpecificationIndex(); + if (specificationIndex >= 0) { + Specification specification = (Specification) values[specificationIndex]; + Root root = criteriaQuery.getRoots().iterator().next(); + BlazeCriteriaBuilder criteriaBuilder = blazeCriteriaQuery.getCriteriaBuilder(); + Predicate predicate = specification.toPredicate(root, criteriaQuery, criteriaBuilder); + criteriaQuery.where(predicate); + } + } + protected FixedJpaQueryCreator createCreator(ParametersParameterAccessor accessor, PersistenceProvider persistenceProvider) { BlazeCriteriaQuery cq = BlazeCriteria.get(getEntityManager(), cbf, Long.class); diff --git a/integration/spring-data/base/src/main/java/com/blazebit/persistence/spring/data/base/query/JpaParameters.java b/integration/spring-data/base/src/main/java/com/blazebit/persistence/spring/data/base/query/JpaParameters.java index 68a2651a0a..c102bba8de 100644 --- a/integration/spring-data/base/src/main/java/com/blazebit/persistence/spring/data/base/query/JpaParameters.java +++ b/integration/spring-data/base/src/main/java/com/blazebit/persistence/spring/data/base/query/JpaParameters.java @@ -16,6 +16,7 @@ package com.blazebit.persistence.spring.data.base.query; import org.springframework.core.MethodParameter; +import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.Temporal; import com.blazebit.persistence.spring.data.annotation.OptionalParam; @@ -71,6 +72,34 @@ public JpaParameters getOptionalParameters() { return createFrom(parameters); } + /** + * Returns the index of the {@link Specification} {@link Method} parameter if available. Will return {@literal -1} if there + * is no {@link Specification} parameter in the {@link Method}'s parameter list. + * + * @return the index of the specification parameter, or -1 if not present + */ + public int getSpecificationIndex() { + int index = 0; + + for (JpaParameter candidate : this) { + if (candidate.isSpecificationParameter()) { + return index; + } + ++index; + } + + return -1; + } + + /** + * Returns whether the method the {@link Parameters} was created for contains a {@link Specification} parameter. + * + * @return true if the methods has a specification parameter + */ + public boolean hasSpecificationParameter() { + return getSpecificationIndex() >= 0; + } + /* * (non-Javadoc) * @see org.springframework.data.repository.query.Parameters#createParameter(org.springframework.core.MethodParameter) @@ -141,13 +170,17 @@ public boolean isBindable() { @Override public boolean isSpecialParameter() { - return super.isSpecialParameter() || isOptionalParameter(); + return super.isSpecialParameter() || isOptionalParameter() || isSpecificationParameter(); } boolean isOptionalParameter() { return optional != null; } + boolean isSpecificationParameter() { + return Specification.class.isAssignableFrom(parameter.getParameterType()); + } + /** * @return {@literal true} if this parameter is of type {@link Date} and has an {@link Temporal} annotation. */ diff --git a/integration/spring-data/testsuite/src/test/java/com/blazebit/persistence/spring/data/testsuite/DocumentRepositoryTest.java b/integration/spring-data/testsuite/src/test/java/com/blazebit/persistence/spring/data/testsuite/DocumentRepositoryTest.java index f7400097b7..7d890301dc 100644 --- a/integration/spring-data/testsuite/src/test/java/com/blazebit/persistence/spring/data/testsuite/DocumentRepositoryTest.java +++ b/integration/spring-data/testsuite/src/test/java/com/blazebit/persistence/spring/data/testsuite/DocumentRepositoryTest.java @@ -541,6 +541,29 @@ public void testFindWithOptionalParameterAndPageable() { assertEquals(param, optionalParameter); } + @Test + public void testFindAllBySpecWithOptionalParameter() { + // Given + final Document d3 = createDocument("d3", null, 3L, null); + final Document d2 = createDocument("d2", null, 2L, null); + final Document d1 = createDocument("d1", null, 1L, null); + + final String param = "Foo"; + + // When + List actual = documentRepository.findAll(new Specification() { + + @Override + public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) { + return criteriaBuilder.ge(root.get("age"), 2L); + } + }, param); + + // Then + assertEquals(2, actual.size()); + assertEquals(actual.get(0).getOptionalParameter(), param); + } + private List getIdsFromViews(Iterable views) { List ids = new ArrayList<>(); for (DocumentAccessor view : views) { diff --git a/integration/spring-data/testsuite/src/test/java/com/blazebit/persistence/spring/data/testsuite/repository/DocumentRepository.java b/integration/spring-data/testsuite/src/test/java/com/blazebit/persistence/spring/data/testsuite/repository/DocumentRepository.java index 4a576d0fb0..66d0930952 100644 --- a/integration/spring-data/testsuite/src/test/java/com/blazebit/persistence/spring/data/testsuite/repository/DocumentRepository.java +++ b/integration/spring-data/testsuite/src/test/java/com/blazebit/persistence/spring/data/testsuite/repository/DocumentRepository.java @@ -26,6 +26,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; +import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.NoRepositoryBean; @@ -75,4 +76,6 @@ public interface DocumentRepository extends EntityViewRepository, En List findByName(String name, @OptionalParam("optionalParameter") String optionalParameter); Page findByNameOrderById(String name, Pageable pageable, @OptionalParam("optionalParameter") String optionalParameter); + + List findAll(Specification specification, @OptionalParam("optionalParameter") String optionalParameter); }