From d88f823c75ac40bd8dbb4e93cfc8bd423eb5e6e8 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Fri, 27 Nov 2015 23:11:10 +0100 Subject: [PATCH] DATAJPA-804 - Support for projections on query methods. Based on the work for DATACMNS-89 we now use the metadata exposed by ResourceProcessor to optimize queries that are to be projected on the query execution level. If a projection interface is used that's not using any dynamic expression, we now explicitly query for a JPA Tuple consisting of all properties required for the projection interface. The same applies to DTOs that use an @PersistenceConstructor. Related tickets: DATACMNS-89. --- .../repository/query/AbstractJpaQuery.java | 74 +++++++-- .../query/AbstractStringBasedJpaQuery.java | 10 +- .../query/JpaCountQueryCreator.java | 12 +- .../jpa/repository/query/JpaQueryCreator.java | 62 +++++-- .../jpa/repository/query/JpaQueryFactory.java | 12 +- .../query/JpaQueryLookupStrategy.java | 19 ++- .../jpa/repository/query/JpaQueryMethod.java | 16 +- .../repository/query/PartTreeJpaQuery.java | 25 ++- .../query/StoredProcedureJpaQuery.java | 2 +- .../EclipseLinkMetamodelIntegrationTests.java | 8 + .../HibernateMetamodelIntegrationTests.java | 8 + .../MetamodelIntegrationTests.java | 27 ++- .../OpenJpaMetamodelIntegrationTests.java | 8 + .../query/AbstractJpaQueryTests.java | 39 ++--- .../JpaQueryLookupStrategyUnitTests.java | 6 +- .../query/JpaQueryMethodUnitTests.java | 154 +++++++++--------- .../repository/query/NamedQueryUnitTests.java | 9 +- .../PartTreeJpaQueryIntegrationTests.java | 19 ++- .../query/SimpleJpaQueryUnitTests.java | 14 +- 19 files changed, 344 insertions(+), 180 deletions(-) diff --git a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java index 8d47fe3e2..b8ec62d3f 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java @@ -15,14 +15,18 @@ */ package org.springframework.data.jpa.repository.query; +import java.util.HashMap; import java.util.Map; import javax.persistence.EntityManager; import javax.persistence.LockModeType; import javax.persistence.Query; import javax.persistence.QueryHint; +import javax.persistence.Tuple; +import javax.persistence.TupleElement; import javax.persistence.TypedQuery; +import org.springframework.core.convert.converter.Converter; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.query.JpaQueryExecution.CollectionExecution; import org.springframework.data.jpa.repository.query.JpaQueryExecution.ModifyingExecution; @@ -31,7 +35,9 @@ import org.springframework.data.jpa.repository.query.JpaQueryExecution.Procedure import org.springframework.data.jpa.repository.query.JpaQueryExecution.SingleEntityExecution; import org.springframework.data.jpa.repository.query.JpaQueryExecution.SlicedExecution; import org.springframework.data.jpa.repository.query.JpaQueryExecution.StreamExecution; +import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.query.ResultProcessor; import org.springframework.util.Assert; /** @@ -49,6 +55,7 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { * Creates a new {@link AbstractJpaQuery} from the given {@link JpaQueryMethod}. * * @param method + * @param resultFactory * @param em */ public AbstractJpaQuery(JpaQueryMethod method, EntityManager em) { @@ -62,30 +69,24 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { /* * (non-Javadoc) - * - * @see - * org.springframework.data.repository.query.RepositoryQuery#getQueryMethod - * () + * @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod() */ public JpaQueryMethod getQueryMethod() { - return method; } /** - * @return the em + * Returns the {@link EntityManager}. + * + * @return will never be {@literal null}. */ protected EntityManager getEntityManager() { - return em; } /* * (non-Javadoc) - * - * @see - * org.springframework.data.repository.query.RepositoryQuery#execute(java - * .lang.Object[]) + * @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[]) */ public Object execute(Object[] parameters) { return doExecute(getExecution(), parameters); @@ -97,7 +98,13 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { * @return */ private Object doExecute(JpaQueryExecution execution, Object[] values) { - return execution.execute(this, values); + + Object result = execution.execute(this, values); + + ParametersParameterAccessor accessor = new ParametersParameterAccessor(method.getParameters(), values); + ResultProcessor withDynamicProjection = method.getResultProcessor().withDynamicProjection(accessor); + + return withDynamicProjection.processResult(result, TupleConverter.INSTANCE); } protected JpaQueryExecution getExecution() { @@ -212,4 +219,47 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { * @return */ protected abstract Query doCreateCountQuery(Object[] values); + + private static enum TupleConverter implements Converter { + + INSTANCE; + + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) + */ + @Override + public Object convert(Object source) { + + if (!(source instanceof Tuple)) { + return source; + } + + Tuple tuple = (Tuple) source; + Map result = new HashMap(); + + for (TupleElement element : tuple.getElements()) { + + String alias = element.getAlias(); + + if (alias == null || isIndexAsString(alias)) { + throw new IllegalStateException("No aliases found in result tuple! Make sure your query defines aliases!"); + } + + result.put(element.getAlias(), tuple.get(element)); + } + + return result; + } + + private static boolean isIndexAsString(String source) { + + try { + Integer.parseInt(source); + return true; + } catch (NumberFormatException o_O) { + return false; + } + } + } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java index f466ee6c5..a6127c3ee 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java @@ -17,10 +17,13 @@ package org.springframework.data.jpa.repository.query; import javax.persistence.EntityManager; import javax.persistence.Query; +import javax.persistence.Tuple; import org.springframework.data.repository.query.EvaluationContextProvider; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.ResultProcessor; +import org.springframework.data.repository.query.ReturnedType; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.util.Assert; @@ -96,7 +99,12 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery { * @return */ public Query createJpaQuery(String queryString) { - return getEntityManager().createQuery(queryString); + + ResultProcessor resultFactory = getQueryMethod().getResultProcessor(); + ReturnedType returnedType = resultFactory.getReturnedType(); + EntityManager em = getEntityManager(); + + return returnedType.isProjecting() ? em.createQuery(queryString, Tuple.class) : em.createQuery(queryString); } /* diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreator.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreator.java index d7761494b..ff111fc83 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreator.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreator.java @@ -17,10 +17,12 @@ package org.springframework.data.jpa.repository.query; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Expression; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import org.springframework.data.domain.Sort; +import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.repository.query.parser.PartTree; /** @@ -38,9 +40,9 @@ public class JpaCountQueryCreator extends JpaQueryCreator { * @param parameters * @param em */ - public JpaCountQueryCreator(PartTree tree, Class domainClass, CriteriaBuilder builder, + public JpaCountQueryCreator(PartTree tree, ReturnedType type, CriteriaBuilder builder, ParameterMetadataProvider provider) { - super(tree, domainClass, builder, provider); + super(tree, type, builder, provider); } /* @@ -48,10 +50,10 @@ public class JpaCountQueryCreator extends JpaQueryCreator { * @see org.springframework.data.jpa.repository.query.JpaQueryCreator#complete(javax.persistence.criteria.Predicate, org.springframework.data.domain.Sort, javax.persistence.criteria.CriteriaQuery, javax.persistence.criteria.CriteriaBuilder, javax.persistence.criteria.Root) */ @Override - protected CriteriaQuery complete(Predicate predicate, Sort sort, CriteriaQuery query, - CriteriaBuilder builder, Root root) { + protected CriteriaQuery complete(Predicate predicate, Sort sort, + CriteriaQuery query, CriteriaBuilder builder, Root root) { - CriteriaQuery select = query.select(builder.count(root)); + CriteriaQuery select = query.select((Expression) builder.count(root)); return predicate == null ? select : select.where(predicate); } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java index 877798a54..33b744507 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java @@ -18,6 +18,7 @@ package org.springframework.data.jpa.repository.query; import static org.springframework.data.jpa.repository.query.QueryUtils.*; import static org.springframework.data.repository.query.parser.Part.Type.*; +import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -28,10 +29,12 @@ import javax.persistence.criteria.Expression; import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; +import javax.persistence.criteria.Selection; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata; import org.springframework.data.mapping.PropertyPath; +import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.repository.query.parser.AbstractQueryCreator; import org.springframework.data.repository.query.parser.Part; import org.springframework.data.repository.query.parser.Part.Type; @@ -43,12 +46,13 @@ import org.springframework.util.Assert; * * @author Oliver Gierke */ -public class JpaQueryCreator extends AbstractQueryCreator, Predicate> { +public class JpaQueryCreator extends AbstractQueryCreator, Predicate> { private final CriteriaBuilder builder; private final Root root; - private final CriteriaQuery query; + private final CriteriaQuery query; private final ParameterMetadataProvider provider; + private final ReturnedType returnedType; /** * Create a new {@link JpaQueryCreator}. @@ -58,15 +62,21 @@ public class JpaQueryCreator extends AbstractQueryCreator, * @param accessor * @param em */ - public JpaQueryCreator(PartTree tree, Class domainClass, CriteriaBuilder builder, + public JpaQueryCreator(PartTree tree, ReturnedType type, CriteriaBuilder builder, ParameterMetadataProvider provider) { super(tree); + Class typeToRead = type.getTypeToRead(); + + CriteriaQuery criteriaQuery = typeToRead == null ? builder.createTupleQuery() + : builder.createQuery(typeToRead); + this.builder = builder; - this.query = builder.createQuery().distinct(tree.isDistinct()); - this.root = query.from(domainClass); + this.query = criteriaQuery.distinct(tree.isDistinct()); + this.root = query.from(type.getDomainType()); this.provider = provider; + this.returnedType = type; } /** @@ -94,7 +104,6 @@ public class JpaQueryCreator extends AbstractQueryCreator, */ @Override protected Predicate and(Part part, Predicate base, Iterator iterator) { - return builder.and(base, toPredicate(part, root)); } @@ -104,7 +113,6 @@ public class JpaQueryCreator extends AbstractQueryCreator, */ @Override protected Predicate or(Predicate base, Predicate predicate) { - return builder.or(base, predicate); } @@ -114,8 +122,7 @@ public class JpaQueryCreator extends AbstractQueryCreator, * and {@link CriteriaBuilder}. */ @Override - protected final CriteriaQuery complete(Predicate predicate, Sort sort) { - + protected final CriteriaQuery complete(Predicate predicate, Sort sort) { return complete(predicate, sort, query, builder, root); } @@ -129,10 +136,24 @@ public class JpaQueryCreator extends AbstractQueryCreator, * @param builder * @return */ - protected CriteriaQuery complete(Predicate predicate, Sort sort, CriteriaQuery query, - CriteriaBuilder builder, Root root) { + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected CriteriaQuery complete(Predicate predicate, Sort sort, + CriteriaQuery query, CriteriaBuilder builder, Root root) { - CriteriaQuery select = this.query.select(root).orderBy(QueryUtils.toOrders(sort, root, builder)); + if (returnedType.needsCustomConstruction()) { + + List> selections = new ArrayList>(); + + for (String property : returnedType.getInputProperties()) { + selections.add(root.get(property).alias(property)); + } + + query = query.multiselect(selections); + } else { + query = query.select((Root) root); + } + + CriteriaQuery select = query.orderBy(QueryUtils.toOrders(sort, root, builder)); return predicate == null ? select : select.where(predicate); } @@ -148,6 +169,23 @@ public class JpaQueryCreator extends AbstractQueryCreator, return new PredicateBuilder(part, root).build(); } + /** + * Returns a path to a {@link Comparable}. + * + * @param root + * @param part + * @return + */ + @SuppressWarnings({ "rawtypes" }) + private Expression getComparablePath(Root root, Part part) { + + return getTypedPath(root, part); + } + + private Expression getTypedPath(Root root, Part part) { + return toExpressionRecursively(root, part.getProperty()); + } + /** * Simple builder to contain logic to create JPA {@link Predicate}s from {@link Part}s. * diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java index 48ed62805..20d2a94ed 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java @@ -41,16 +41,16 @@ enum JpaQueryFactory { * Creates a {@link RepositoryQuery} from the given {@link QueryMethod} that is potentially annotated with * {@link Query}. * - * @param queryMethod must not be {@literal null}. + * @param method must not be {@literal null}. * @param em must not be {@literal null}. * @param evaluationContextProvider * @return the {@link RepositoryQuery} derived from the annotation or {@code null} if no annotation found. */ - AbstractJpaQuery fromQueryAnnotation(JpaQueryMethod queryMethod, EntityManager em, + AbstractJpaQuery fromQueryAnnotation(JpaQueryMethod method, EntityManager em, EvaluationContextProvider evaluationContextProvider) { - LOG.debug("Looking up query for method {}", queryMethod.getName()); - return fromMethodWithQueryString(queryMethod, em, queryMethod.getAnnotatedQuery(), evaluationContextProvider); + LOG.debug("Looking up query for method {}", method.getName()); + return fromMethodWithQueryString(method, em, method.getAnnotatedQuery(), evaluationContextProvider); } /** @@ -69,8 +69,8 @@ enum JpaQueryFactory { return null; } - return method.isNativeQuery() ? new NativeJpaQuery(method, em, queryString, evaluationContextProvider, PARSER) : // - new SimpleJpaQuery(method, em, queryString, evaluationContextProvider, PARSER); + return method.isNativeQuery() ? new NativeJpaQuery(method, em, queryString, evaluationContextProvider, PARSER) + : new SimpleJpaQuery(method, em, queryString, evaluationContextProvider, PARSER); } /** diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java index 7b1606394..c1af02c7e 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java @@ -20,6 +20,7 @@ import java.lang.reflect.Method; import javax.persistence.EntityManager; import org.springframework.data.jpa.provider.QueryExtractor; +import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.query.EvaluationContextProvider; @@ -65,12 +66,14 @@ public final class JpaQueryLookupStrategy { this.provider = extractor; } - /* + /* * (non-Javadoc) - * @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.repository.core.NamedQueries) + * @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.projection.ProjectionFactory, org.springframework.data.repository.core.NamedQueries) */ - public final RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, NamedQueries namedQueries) { - return resolveQuery(new JpaQueryMethod(method, metadata, provider), em, namedQueries); + @Override + public final RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, + NamedQueries namedQueries) { + return resolveQuery(new JpaQueryMethod(method, metadata, factory, provider), em, namedQueries); } protected abstract RepositoryQuery resolveQuery(JpaQueryMethod method, EntityManager em, NamedQueries namedQueries); @@ -94,8 +97,8 @@ public final class JpaQueryLookupStrategy { try { return new PartTreeJpaQuery(method, em); } catch (IllegalArgumentException e) { - throw new IllegalArgumentException(String.format("Could not create query metamodel for method %s!", - method.toString()), e); + throw new IllegalArgumentException( + String.format("Could not create query metamodel for method %s!", method.toString()), e); } } @@ -157,8 +160,8 @@ public final class JpaQueryLookupStrategy { return query; } - throw new IllegalStateException(String.format( - "Did neither find a NamedQuery nor an annotated query for method %s!", method)); + throw new IllegalStateException( + String.format("Did neither find a NamedQuery nor an annotated query for method %s!", method)); } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java index 7c22177a1..c1bc2dc7c 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java @@ -35,6 +35,7 @@ import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.QueryHints; +import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; @@ -77,9 +78,10 @@ public class JpaQueryMethod extends QueryMethod { * @param extractor must not be {@literal null} * @param metadata must not be {@literal null} */ - public JpaQueryMethod(Method method, RepositoryMetadata metadata, QueryExtractor extractor) { + public JpaQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory, + QueryExtractor extractor) { - super(method, metadata); + super(method, metadata, factory); Assert.notNull(method, "Method must not be null!"); Assert.notNull(extractor, "Query extractor must not be null!"); @@ -108,9 +110,9 @@ public class JpaQueryMethod extends QueryMethod { if (!annotatedQuery.contains(String.format(":%s", parameter.getName())) && !annotatedQuery.contains(String.format("#%s", parameter.getName()))) { - throw new IllegalStateException(String.format( - "Using named parameters for method %s but parameter '%s' not found in annotated query '%s'!", method, - parameter.getName(), annotatedQuery)); + throw new IllegalStateException( + String.format("Using named parameters for method %s but parameter '%s' not found in annotated query '%s'!", + method, parameter.getName(), annotatedQuery)); } } } @@ -298,8 +300,8 @@ public class JpaQueryMethod extends QueryMethod { private T getAnnotationValue(String attribute, Class type) { Query annotation = method.getAnnotation(Query.class); - Object value = annotation == null ? AnnotationUtils.getDefaultValue(Query.class, attribute) : AnnotationUtils - .getValue(annotation, attribute); + Object value = annotation == null ? AnnotationUtils.getDefaultValue(Query.class, attribute) + : AnnotationUtils.getValue(annotation, attribute); return type.cast(value); } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java index 6156f6802..ac8189ae2 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java @@ -28,6 +28,7 @@ import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.jpa.repository.query.JpaQueryExecution.DeleteExecution; import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata; import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.ResultProcessor; import org.springframework.data.repository.query.parser.PartTree; /** @@ -50,19 +51,21 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { * Creates a new {@link PartTreeJpaQuery}. * * @param method must not be {@literal null}. + * @param factory must not be {@literal null}. * @param em must not be {@literal null}. */ public PartTreeJpaQuery(JpaQueryMethod method, EntityManager em) { super(method, em); this.em = em; - this.domainClass = method.getEntityInformation().getJavaType(); this.tree = new PartTree(method.getName(), domainClass); this.parameters = method.getParameters(); - this.countQuery = new CountQueryPreparer(parameters.potentiallySortsDynamically()); - this.query = tree.isCountProjection() ? countQuery : new QueryPreparer(parameters.potentiallySortsDynamically()); + boolean recreationRequired = parameters.hasDynamicProjection() || parameters.potentiallySortsDynamically(); + + this.countQuery = new CountQueryPreparer(recreationRequired); + this.query = tree.isCountProjection() ? countQuery : new QueryPreparer(recreationRequired); } /* @@ -189,10 +192,13 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { CriteriaBuilder builder = entityManager.getCriteriaBuilder(); PersistenceProvider persistenceProvider = PersistenceProvider.fromEntityManager(entityManager); - ParameterMetadataProvider provider = accessor == null ? new ParameterMetadataProvider(builder, parameters, - persistenceProvider) : new ParameterMetadataProvider(builder, accessor, persistenceProvider); + ParameterMetadataProvider provider = accessor == null + ? new ParameterMetadataProvider(builder, parameters, persistenceProvider) + : new ParameterMetadataProvider(builder, accessor, persistenceProvider); - return new JpaQueryCreator(tree, domainClass, builder, provider); + ResultProcessor resultFactory = getQueryMethod().getResultProcessor().withDynamicProjection(accessor); + + return new JpaQueryCreator(tree, resultFactory.getReturnedType(), builder, provider); } /** @@ -241,10 +247,11 @@ public class PartTreeJpaQuery extends AbstractJpaQuery { CriteriaBuilder builder = entityManager.getCriteriaBuilder(); PersistenceProvider persistenceProvider = PersistenceProvider.fromEntityManager(entityManager); - ParameterMetadataProvider provider = accessor == null ? new ParameterMetadataProvider(builder, parameters, - persistenceProvider) : new ParameterMetadataProvider(builder, accessor, persistenceProvider); + ParameterMetadataProvider provider = accessor == null + ? new ParameterMetadataProvider(builder, parameters, persistenceProvider) + : new ParameterMetadataProvider(builder, accessor, persistenceProvider); - return new JpaCountQueryCreator(tree, domainClass, builder, provider); + return new JpaCountQueryCreator(tree, getQueryMethod().getResultProcessor().getReturnedType(), builder, provider); } /** diff --git a/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java index b6e610781..8e041c382 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java @@ -116,7 +116,7 @@ class StoredProcedureJpaQuery extends AbstractJpaQuery { JpaParameters parameters = getQueryMethod().getParameters(); return useNamedParameters && StringUtils.hasText(outputParameterName) ? // - storedProcedureQuery.getOutputParameterValue(outputParameterName) + storedProcedureQuery.getOutputParameterValue(outputParameterName) : storedProcedureQuery.getOutputParameterValue(parameters.getNumberOfParameters() + 1); } diff --git a/src/test/java/org/springframework/data/jpa/infrastructure/EclipseLinkMetamodelIntegrationTests.java b/src/test/java/org/springframework/data/jpa/infrastructure/EclipseLinkMetamodelIntegrationTests.java index 0bdd8edd1..44d03c1e0 100644 --- a/src/test/java/org/springframework/data/jpa/infrastructure/EclipseLinkMetamodelIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/infrastructure/EclipseLinkMetamodelIntegrationTests.java @@ -42,4 +42,12 @@ public class EclipseLinkMetamodelIntegrationTests extends MetamodelIntegrationTe @Ignore @Override public void pathToEntityIsOfBindableTypeEntityType() {} + + /** + * TODO: Remove, once https://bugs.eclipse.org/bugs/show_bug.cgi?id=289141 is fixed. + */ + @Test + @Ignore + @Override + public void doesNotExposeAliasForTupleIfNoneDefined() {} } diff --git a/src/test/java/org/springframework/data/jpa/infrastructure/HibernateMetamodelIntegrationTests.java b/src/test/java/org/springframework/data/jpa/infrastructure/HibernateMetamodelIntegrationTests.java index 379f30e70..b4e9f51e8 100644 --- a/src/test/java/org/springframework/data/jpa/infrastructure/HibernateMetamodelIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/infrastructure/HibernateMetamodelIntegrationTests.java @@ -35,4 +35,12 @@ public class HibernateMetamodelIntegrationTests extends MetamodelIntegrationTest @Ignore @Override public void considersOneToOneAttributeAnAssociation() {} + + /** + * @see https://hibernate.atlassian.net/browse/HHH-10341 + */ + @Test + @Ignore + @Override + public void doesNotExposeAliasForTupleIfNoneDefined() {} } diff --git a/src/test/java/org/springframework/data/jpa/infrastructure/MetamodelIntegrationTests.java b/src/test/java/org/springframework/data/jpa/infrastructure/MetamodelIntegrationTests.java index 4f96de0d8..ec4ddb9b7 100644 --- a/src/test/java/org/springframework/data/jpa/infrastructure/MetamodelIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/infrastructure/MetamodelIntegrationTests.java @@ -15,12 +15,17 @@ */ package org.springframework.data.jpa.infrastructure; -import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import java.util.List; + import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; +import javax.persistence.Tuple; +import javax.persistence.TupleElement; +import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Path; @@ -35,6 +40,7 @@ import org.junit.runner.RunWith; import org.springframework.data.jpa.domain.sample.User; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; /** * @author Oliver Gierke @@ -74,4 +80,23 @@ public abstract class MetamodelIntegrationTests { assertThat(query.getParameter(1), is(notNullValue())); } + + @Test + @Transactional + public void doesNotExposeAliasForTupleIfNoneDefined() { + + User user = new User(); + user.setFirstname("Dave"); + user.setEmailAddress("email"); + + em.persist(user); + + TypedQuery query = em.createQuery("SELECT u.firstname from User u", Tuple.class); + + List result = query.getResultList(); + List> elements = result.get(0).getElements(); + + assertThat(elements, hasSize(1)); + assertThat(elements.get(0).getAlias(), is(nullValue())); + } } diff --git a/src/test/java/org/springframework/data/jpa/infrastructure/OpenJpaMetamodelIntegrationTests.java b/src/test/java/org/springframework/data/jpa/infrastructure/OpenJpaMetamodelIntegrationTests.java index 2bd5b0fd4..739653ec6 100644 --- a/src/test/java/org/springframework/data/jpa/infrastructure/OpenJpaMetamodelIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/infrastructure/OpenJpaMetamodelIntegrationTests.java @@ -31,4 +31,12 @@ public class OpenJpaMetamodelIntegrationTests extends MetamodelIntegrationTests @Ignore @Override public void canAccessParametersByIndexForNativeQueries() {} + + /** + * TODO: Remove once https://issues.apache.org/jira/browse/OPENJPA-2618 is fixed. + */ + @Test + @Ignore + @Override + public void doesNotExposeAliasForTupleIfNoneDefined() {} } diff --git a/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java b/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java index 5fd9fe17f..ea86d52d6 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2011 the original author or authors. + * Copyright 2008-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,11 +35,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.jpa.provider.PersistenceProvider; -import org.springframework.data.jpa.provider.QueryExtractor; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType; import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.QueryHints; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.test.context.ContextConfiguration; @@ -75,10 +75,7 @@ public class AbstractJpaQueryTests { @Test public void addsHintsToQueryObject() throws Exception { - Method method = SampleRepository.class.getMethod("findByLastname", String.class); - QueryExtractor provider = PersistenceProvider.fromEntityManager(em); - JpaQueryMethod queryMethod = new JpaQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class), - provider); + JpaQueryMethod queryMethod = getMethod("findByLastname", String.class); AbstractJpaQuery jpaQuery = new DummyJpaQuery(queryMethod, em); @@ -96,11 +93,7 @@ public class AbstractJpaQueryTests { @Test public void skipsHintsForCountQueryIfConfigured() throws Exception { - Method method = SampleRepository.class.getMethod("findByFirstname", String.class); - QueryExtractor provider = PersistenceProvider.fromEntityManager(em); - JpaQueryMethod queryMethod = new JpaQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class), - provider); - + JpaQueryMethod queryMethod = getMethod("findByFirstname", String.class); AbstractJpaQuery jpaQuery = new DummyJpaQuery(queryMethod, em); Query result = jpaQuery.createQuery(new Object[] { "Dave" }); @@ -118,10 +111,7 @@ public class AbstractJpaQueryTests { when(query.setLockMode(any(LockModeType.class))).thenReturn(query); - Method method = SampleRepository.class.getMethod("findOneLocked", Integer.class); - QueryExtractor provider = PersistenceProvider.fromEntityManager(em); - JpaQueryMethod queryMethod = new JpaQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class), - provider); + JpaQueryMethod queryMethod = getMethod("findOneLocked", Integer.class); AbstractJpaQuery jpaQuery = new DummyJpaQuery(queryMethod, em); Query result = jpaQuery.createQuery(new Object[] { Integer.valueOf(1) }); @@ -137,10 +127,7 @@ public class AbstractJpaQueryTests { Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em)); - Method findAllMethod = SampleRepository.class.getMethod("findAll"); - QueryExtractor provider = PersistenceProvider.fromEntityManager(em); - JpaQueryMethod queryMethod = new JpaQueryMethod(findAllMethod, - new DefaultRepositoryMetadata(SampleRepository.class), provider); + JpaQueryMethod queryMethod = getMethod("findAll"); javax.persistence.EntityGraph entityGraph = em.getEntityGraph("User.overview"); @@ -159,10 +146,7 @@ public class AbstractJpaQueryTests { Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em)); - Method getByIdMethod = SampleRepository.class.getMethod("getById", Integer.class); - QueryExtractor provider = PersistenceProvider.fromEntityManager(em); - JpaQueryMethod queryMethod = new JpaQueryMethod(getByIdMethod, - new DefaultRepositoryMetadata(SampleRepository.class), provider); + JpaQueryMethod queryMethod = getMethod("getById", Integer.class); javax.persistence.EntityGraph entityGraph = em.getEntityGraph("User.detail"); @@ -172,6 +156,15 @@ public class AbstractJpaQueryTests { verify(result).setHint("javax.persistence.loadgraph", entityGraph); } + private JpaQueryMethod getMethod(String name, Class... parameterTypes) throws Exception { + + Method method = SampleRepository.class.getMethod(name, parameterTypes); + PersistenceProvider persistenceProvider = PersistenceProvider.fromEntityManager(em); + + return new JpaQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class), + new SpelAwareProxyProjectionFactory(), persistenceProvider); + } + interface SampleRepository extends Repository { @QueryHints({ @QueryHint(name = "foo", value = "bar") }) diff --git a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java index 778c48bb2..0d0eb618d 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java @@ -37,6 +37,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.jpa.provider.QueryExtractor; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.RepositoryMetadata; @@ -60,6 +61,7 @@ public class JpaQueryLookupStrategyUnitTests { @Mock EntityManagerFactory emf; @Mock QueryExtractor extractor; @Mock NamedQueries namedQueries; + @Mock ProjectionFactory projectionFactory; public @Rule ExpectedException exception = ExpectedException.none(); @@ -85,7 +87,7 @@ public class JpaQueryLookupStrategyUnitTests { when(em.createQuery(anyString())).thenThrow(reference); try { - strategy.resolveQuery(method, metadata, namedQueries); + strategy.resolveQuery(method, metadata, projectionFactory, namedQueries); } catch (Exception e) { assertThat(e, is(instanceOf(IllegalArgumentException.class))); assertThat(e.getCause(), is(reference)); @@ -107,7 +109,7 @@ public class JpaQueryLookupStrategyUnitTests { exception.expectMessage("Cannot use native queries with dynamic sorting and/or pagination in method"); exception.expectMessage(method.toString()); - strategy.resolveQuery(method, metadata, namedQueries); + strategy.resolveQuery(method, metadata, projectionFactory, namedQueries); } interface UserRepository extends Repository { diff --git a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java index 40c5c4f2e..0e176f515 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java @@ -17,6 +17,7 @@ package org.springframework.data.jpa.repository.query; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; import java.lang.annotation.Retention; @@ -45,6 +46,9 @@ import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.QueryHints; import org.springframework.data.jpa.repository.sample.UserRepository; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; +import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.query.Param; @@ -64,10 +68,10 @@ public class JpaQueryMethodUnitTests { @Mock QueryExtractor extractor; @Mock RepositoryMetadata metadata; + ProjectionFactory factory = new SpelAwareProxyProjectionFactory(); - Method repositoryMethod, invalidReturnType, pageableAndSort, pageableTwice, sortableTwice, modifyingMethod, - nativeQuery, namedQuery, findWithLockMethod, invalidNamedParameter, findsProjections, findsProjection, - withMetaAnnotation, queryMethodWithCustomEntityFetchGraph; + Method invalidReturnType, pageableAndSort, pageableTwice, sortableTwice, findWithLockMethod, findsProjections, + findsProjection, queryMethodWithCustomEntityFetchGraph; /** * @throws Exception @@ -75,35 +79,25 @@ public class JpaQueryMethodUnitTests { @Before public void setUp() throws Exception { - repositoryMethod = UserRepository.class.getMethod("findByLastname", String.class); - invalidReturnType = InvalidRepository.class.getMethod(METHOD_NAME, String.class, Pageable.class); pageableAndSort = InvalidRepository.class.getMethod(METHOD_NAME, String.class, Pageable.class, Sort.class); pageableTwice = InvalidRepository.class.getMethod(METHOD_NAME, String.class, Pageable.class, Pageable.class); sortableTwice = InvalidRepository.class.getMethod(METHOD_NAME, String.class, Sort.class, Sort.class); - modifyingMethod = UserRepository.class.getMethod("renameAllUsersTo", String.class); - - nativeQuery = ValidRepository.class.getMethod("findByLastname", String.class); - namedQuery = ValidRepository.class.getMethod("findByNamedQuery"); findWithLockMethod = ValidRepository.class.getMethod("findOneLocked", Integer.class); - invalidNamedParameter = InvalidRepository.class.getMethod("findByAnnotatedQuery", String.class); findsProjections = ValidRepository.class.getMethod("findsProjections"); findsProjection = ValidRepository.class.getMethod("findsProjection"); - withMetaAnnotation = ValidRepository.class.getMethod("withMetaAnnotation"); - queryMethodWithCustomEntityFetchGraph = ValidRepository.class.getMethod("queryMethodWithCustomEntityFetchGraph", Integer.class); } @Test - public void testname() { + public void testname() throws Exception { - JpaQueryMethod method = new JpaQueryMethod(repositoryMethod, new DefaultRepositoryMetadata(UserRepository.class), - extractor); + JpaQueryMethod method = getQueryMethod(UserRepository.class, "findByLastname", String.class); assertEquals("User.findByLastname", method.getNamedQueryName()); assertThat(method.isCollectionQuery(), is(true)); @@ -114,62 +108,61 @@ public class JpaQueryMethodUnitTests { @Test(expected = IllegalArgumentException.class) public void preventsNullRepositoryMethod() { - new JpaQueryMethod(null, metadata, extractor); + new JpaQueryMethod(null, metadata, factory, extractor); } @Test(expected = IllegalArgumentException.class) - public void preventsNullQueryExtractor() { + public void preventsNullQueryExtractor() throws Exception { - new JpaQueryMethod(repositoryMethod, metadata, null); + Method method = UserRepository.class.getMethod("findByLastname", String.class); + new JpaQueryMethod(method, metadata, factory, null); } @Test - public void returnsCorrectName() { + public void returnsCorrectName() throws Exception { - JpaQueryMethod method = new JpaQueryMethod(repositoryMethod, metadata, extractor); - assertEquals(repositoryMethod.getName(), method.getName()); + JpaQueryMethod method = getQueryMethod(UserRepository.class, "findByLastname", String.class); + assertThat(method.getName(), is("findByLastname")); } @Test public void returnsQueryIfAvailable() throws Exception { - JpaQueryMethod method = new JpaQueryMethod(repositoryMethod, metadata, extractor); + JpaQueryMethod method = getQueryMethod(UserRepository.class, "findByLastname", String.class); + assertThat(method.getAnnotatedQuery(), is(nullValue())); - assertNull(method.getAnnotatedQuery()); - - Method repositoryMethod = UserRepository.class.getMethod("findByAnnotatedQuery", String.class); - - assertNotNull(new JpaQueryMethod(repositoryMethod, metadata, extractor).getAnnotatedQuery()); + method = getQueryMethod(UserRepository.class, "findByAnnotatedQuery", String.class); + assertThat(method.getAnnotatedQuery(), is(notNullValue())); } @Test(expected = IllegalStateException.class) public void rejectsInvalidReturntypeOnPagebleFinder() { - new JpaQueryMethod(invalidReturnType, metadata, extractor); + new JpaQueryMethod(invalidReturnType, metadata, factory, extractor); } @Test(expected = IllegalStateException.class) public void rejectsPageableAndSortInFinderMethod() { - new JpaQueryMethod(pageableAndSort, metadata, extractor); + new JpaQueryMethod(pageableAndSort, metadata, factory, extractor); } @Test(expected = IllegalStateException.class) public void rejectsTwoPageableParameters() { - new JpaQueryMethod(pageableTwice, metadata, extractor); + new JpaQueryMethod(pageableTwice, metadata, factory, extractor); } @Test(expected = IllegalStateException.class) public void rejectsTwoSortableParameters() { - new JpaQueryMethod(sortableTwice, metadata, extractor); + new JpaQueryMethod(sortableTwice, metadata, factory, extractor); } @Test - public void recognizesModifyingMethod() { + public void recognizesModifyingMethod() throws Exception { - JpaQueryMethod method = new JpaQueryMethod(modifyingMethod, metadata, extractor); + JpaQueryMethod method = getQueryMethod(UserRepository.class, "renameAllUsersTo", String.class); assertTrue(method.isModifyingQuery()); } @@ -178,7 +171,7 @@ public class JpaQueryMethodUnitTests { Method method = InvalidRepository.class.getMethod("updateMethod", String.class, Pageable.class); - new JpaQueryMethod(method, metadata, extractor); + new JpaQueryMethod(method, metadata, factory, extractor); } @Test(expected = IllegalArgumentException.class) @@ -186,13 +179,13 @@ public class JpaQueryMethodUnitTests { Method method = InvalidRepository.class.getMethod("updateMethod", String.class, Sort.class); - new JpaQueryMethod(method, metadata, extractor); + new JpaQueryMethod(method, metadata, factory, extractor); } @Test - public void discoversHintsCorrectly() { + public void discoversHintsCorrectly() throws Exception { - JpaQueryMethod method = new JpaQueryMethod(repositoryMethod, metadata, extractor); + JpaQueryMethod method = getQueryMethod(UserRepository.class, "findByLastname", String.class); List hints = method.getHints(); assertNotNull(hints); @@ -200,20 +193,28 @@ public class JpaQueryMethodUnitTests { assertThat(hints.get(0).value(), is("bar")); } + private JpaQueryMethod getQueryMethod(Class repositoryInterface, String methodName, Class... parameterTypes) + throws Exception { + + Method method = repositoryInterface.getMethod(methodName, parameterTypes); + DefaultRepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(repositoryInterface); + return new JpaQueryMethod(method, repositoryMetadata, factory, extractor); + } + @Test - public void calculatesNamedQueryNamesCorrectly() throws SecurityException, NoSuchMethodException { + public void calculatesNamedQueryNamesCorrectly() throws Exception { RepositoryMetadata metadata = new DefaultRepositoryMetadata(UserRepository.class); - JpaQueryMethod queryMethod = new JpaQueryMethod(repositoryMethod, metadata, extractor); + JpaQueryMethod queryMethod = getQueryMethod(UserRepository.class, "findByLastname", String.class); assertThat(queryMethod.getNamedQueryName(), is("User.findByLastname")); Method method = UserRepository.class.getMethod("renameAllUsersTo", String.class); - queryMethod = new JpaQueryMethod(method, metadata, extractor); + queryMethod = new JpaQueryMethod(method, metadata, factory, extractor); assertThat(queryMethod.getNamedQueryName(), is("User.renameAllUsersTo")); method = UserRepository.class.getMethod("findSpecialUsersByLastname", String.class); - queryMethod = new JpaQueryMethod(method, metadata, extractor); + queryMethod = new JpaQueryMethod(method, metadata, factory, extractor); assertThat(queryMethod.getNamedQueryName(), is("SpecialUser.findSpecialUsersByLastname")); } @@ -221,9 +222,9 @@ public class JpaQueryMethodUnitTests { * @see DATAJPA-117 */ @Test - public void discoversNativeQuery() { + public void discoversNativeQuery() throws Exception { - JpaQueryMethod method = new JpaQueryMethod(nativeQuery, metadata, extractor); + JpaQueryMethod method = getQueryMethod(ValidRepository.class, "findByLastname", String.class); assertThat(method.isNativeQuery(), is(true)); } @@ -231,8 +232,9 @@ public class JpaQueryMethodUnitTests { * @see DATAJPA-129 */ @Test - public void considersAnnotatedNamedQueryName() { - JpaQueryMethod queryMethod = new JpaQueryMethod(namedQuery, metadata, extractor); + public void considersAnnotatedNamedQueryName() throws Exception { + + JpaQueryMethod queryMethod = getQueryMethod(ValidRepository.class, "findByNamedQuery"); assertThat(queryMethod.getNamedQueryName(), is("HateoasAwareSpringDataWebConfiguration.bar")); } @@ -242,7 +244,7 @@ public class JpaQueryMethodUnitTests { @Test public void discoversLockModeCorrectly() throws Exception { - JpaQueryMethod method = new JpaQueryMethod(findWithLockMethod, metadata, extractor); + JpaQueryMethod method = getQueryMethod(ValidRepository.class, "findOneLocked", Integer.class); LockModeType lockMode = method.getLockModeType(); assertEquals(LockModeType.PESSIMISTIC_WRITE, lockMode); @@ -252,12 +254,9 @@ public class JpaQueryMethodUnitTests { * @see DATAJPA-142 */ @Test - @SuppressWarnings({ "unchecked", "rawtypes" }) - public void returnsDefaultCountQueryName() { + public void returnsDefaultCountQueryName() throws Exception { - when(metadata.getReturnedDomainClass(repositoryMethod)).thenReturn((Class) User.class); - - JpaQueryMethod method = new JpaQueryMethod(repositoryMethod, metadata, extractor); + JpaQueryMethod method = getQueryMethod(UserRepository.class, "findByLastname", String.class); assertThat(method.getNamedCountQueryName(), is("User.findByLastname.count")); } @@ -265,9 +264,9 @@ public class JpaQueryMethodUnitTests { * @see DATAJPA-142 */ @Test - public void returnsDefaultCountQueryNameBasedOnConfiguredNamedQueryName() { + public void returnsDefaultCountQueryNameBasedOnConfiguredNamedQueryName() throws Exception { - JpaQueryMethod method = new JpaQueryMethod(namedQuery, metadata, extractor); + JpaQueryMethod method = getQueryMethod(ValidRepository.class, "findByNamedQuery"); assertThat(method.getNamedCountQueryName(), is("HateoasAwareSpringDataWebConfiguration.bar.count")); } @@ -275,10 +274,10 @@ public class JpaQueryMethodUnitTests { * @see DATAJPA-185 */ @Test - public void rejectsInvalidNamedParameter() { + public void rejectsInvalidNamedParameter() throws Exception { try { - new JpaQueryMethod(invalidNamedParameter, metadata, extractor); + getQueryMethod(InvalidRepository.class, "findByAnnotatedQuery", String.class); fail(); } catch (IllegalStateException e) { // Parameter from query @@ -301,17 +300,17 @@ public class JpaQueryMethodUnitTests { when(metadata.getReturnedDomainClass(findsProjections)).thenReturn((Class) Integer.class); when(metadata.getReturnedDomainClass(findsProjection)).thenReturn((Class) Integer.class); - assertThat(new JpaQueryMethod(findsProjections, metadata, extractor).isQueryForEntity(), is(false)); - assertThat(new JpaQueryMethod(findsProjection, metadata, extractor).isQueryForEntity(), is(false)); + assertThat(new JpaQueryMethod(findsProjections, metadata, factory, extractor).isQueryForEntity(), is(false)); + assertThat(new JpaQueryMethod(findsProjection, metadata, factory, extractor).isQueryForEntity(), is(false)); } /** * @see DATAJPA-345 */ @Test - public void detectsLockAndQueryHintsOnIfUsedAsMetaAnnotation() { + public void detectsLockAndQueryHintsOnIfUsedAsMetaAnnotation() throws Exception { - JpaQueryMethod method = new JpaQueryMethod(withMetaAnnotation, metadata, extractor); + JpaQueryMethod method = getQueryMethod(ValidRepository.class, "withMetaAnnotation"); assertThat(method.getLockModeType(), is(LockModeType.OPTIMISTIC_FORCE_INCREMENT)); assertThat(method.getHints(), hasSize(1)); @@ -324,11 +323,11 @@ public class JpaQueryMethodUnitTests { */ @Test public void shouldStoreJpa21FetchGraphInformationAsHint() { - + doReturn(User.class).when(metadata).getDomainType(); doReturn(User.class).when(metadata).getReturnedDomainClass(queryMethodWithCustomEntityFetchGraph); - JpaQueryMethod method = new JpaQueryMethod(queryMethodWithCustomEntityFetchGraph, metadata, extractor); + JpaQueryMethod method = new JpaQueryMethod(queryMethodWithCustomEntityFetchGraph, metadata, factory, extractor); assertThat(method.getEntityGraph(), is(notNullValue())); assertThat(method.getEntityGraph().getName(), is("User.propertyLoadPath")); @@ -342,9 +341,10 @@ public class JpaQueryMethodUnitTests { public void shouldFindEntityGraphAnnotationOnOverriddenSimpleJpaRepositoryMethod() throws Exception { doReturn(User.class).when(metadata).getDomainType(); - doReturn(User.class).when(metadata).getReturnedDomainClass((Method)any()); - - JpaQueryMethod method = new JpaQueryMethod(JpaRepositoryOverride.class.getMethod("findAll"), metadata, extractor); + doReturn(User.class).when(metadata).getReturnedDomainClass((Method) any()); + + JpaQueryMethod method = new JpaQueryMethod(JpaRepositoryOverride.class.getMethod("findAll"), metadata, factory, + extractor); assertThat(method.getEntityGraph(), is(notNullValue())); assertThat(method.getEntityGraph().getName(), is("User.detail")); @@ -358,15 +358,16 @@ public class JpaQueryMethodUnitTests { public void shouldFindEntityGraphAnnotationOnOverriddenSimpleJpaRepositoryMethodFindOne() throws Exception { doReturn(User.class).when(metadata).getDomainType(); - doReturn(User.class).when(metadata).getReturnedDomainClass((Method)any()); - - JpaQueryMethod method = new JpaQueryMethod(JpaRepositoryOverride.class.getMethod("findOne", Long.class), metadata, extractor); + doReturn(User.class).when(metadata).getReturnedDomainClass((Method) any()); + + JpaQueryMethod method = new JpaQueryMethod(JpaRepositoryOverride.class.getMethod("findOne", Long.class), metadata, + factory, extractor); assertThat(method.getEntityGraph(), is(notNullValue())); assertThat(method.getEntityGraph().getName(), is("User.detail")); assertThat(method.getEntityGraph().getType(), is(EntityGraphType.FETCH)); } - + /** * DATAJPA-696 */ @@ -374,9 +375,10 @@ public class JpaQueryMethodUnitTests { public void shouldFindEntityGraphAnnotationOnQueryMethodGetOneByWithDerivedName() throws Exception { doReturn(User.class).when(metadata).getDomainType(); - doReturn(User.class).when(metadata).getReturnedDomainClass((Method)any()); - - JpaQueryMethod method = new JpaQueryMethod(JpaRepositoryOverride.class.getMethod("getOneById", Long.class), metadata, extractor); + doReturn(User.class).when(metadata).getReturnedDomainClass((Method) any()); + + JpaQueryMethod method = new JpaQueryMethod(JpaRepositoryOverride.class.getMethod("getOneById", Long.class), + metadata, factory, extractor); assertThat(method.getEntityGraph(), is(notNullValue())); assertThat(method.getEntityGraph().getName(), is("User.getOneById")); @@ -388,9 +390,7 @@ public class JpaQueryMethodUnitTests { */ @Test public void allowsPositionalBindingEvenIfParametersAreNamed() throws Exception { - - new JpaQueryMethod(ValidRepository.class.getMethod("queryWithPositionalBinding", String.class), metadata, - extractor); + getQueryMethod(ValidRepository.class, "queryWithPositionalBinding", String.class); } /** @@ -398,7 +398,7 @@ public class JpaQueryMethodUnitTests { * * @author Oliver Gierke */ - static interface InvalidRepository { + static interface InvalidRepository extends Repository { // Invalid return type User findByFirstname(String firstname, Pageable pageable); @@ -429,7 +429,7 @@ public class JpaQueryMethodUnitTests { List findByAnnotatedQuery(@Param("param") String param); } - static interface ValidRepository { + static interface ValidRepository extends Repository { @Query(value = "query", nativeQuery = true) List findByLastname(String lastname); @@ -472,7 +472,7 @@ public class JpaQueryMethodUnitTests { */ @EntityGraph("User.detail") User findOne(Long id); - + /** * DATAJPA-696 */ diff --git a/src/test/java/org/springframework/data/jpa/repository/query/NamedQueryUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/NamedQueryUnitTests.java index 7bb346028..ad371db40 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/NamedQueryUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/NamedQueryUnitTests.java @@ -31,6 +31,8 @@ import org.mockito.runners.MockitoJUnitRunner; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.provider.QueryExtractor; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.query.QueryCreationException; @@ -47,6 +49,7 @@ public class NamedQueryUnitTests { @Mock QueryExtractor extractor; @Mock EntityManager em; @Mock EntityManagerFactory emf; + ProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory(); Method method; @@ -57,7 +60,7 @@ public class NamedQueryUnitTests { method = SampleRepository.class.getMethod("foo", Pageable.class); when(metadata.getDomainType()).thenReturn((Class) String.class); when(metadata.getReturnedDomainClass(method)).thenReturn((Class) String.class); - + when(em.getEntityManagerFactory()).thenReturn(emf); when(emf.createEntityManager()).thenReturn(em); } @@ -66,7 +69,7 @@ public class NamedQueryUnitTests { public void rejectsPersistenceProviderIfIncapableOfExtractingQueriesAndPagebleBeingUsed() { when(extractor.canExtractQuery()).thenReturn(false); - JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, extractor); + JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, projectionFactory, extractor); when(em.createNamedQuery(queryMethod.getNamedCountQueryName())).thenThrow(new IllegalArgumentException()); NamedQuery.lookupFrom(queryMethod, em); @@ -79,7 +82,7 @@ public class NamedQueryUnitTests { public void doesNotRejectPersistenceProviderIfNamedCountQueryIsAvailable() { when(extractor.canExtractQuery()).thenReturn(false); - JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, extractor); + JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, projectionFactory, extractor); when(em.createNamedQuery(queryMethod.getNamedCountQueryName())).thenReturn(null); NamedQuery query = (NamedQuery) NamedQuery.lookupFrom(queryMethod, em); diff --git a/src/test/java/org/springframework/data/jpa/repository/query/PartTreeJpaQueryIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/query/PartTreeJpaQueryIntegrationTests.java index bf0815996..7b7ebe5c0 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/PartTreeJpaQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/PartTreeJpaQueryIntegrationTests.java @@ -44,6 +44,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.jpa.repository.Temporal; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.query.Param; @@ -72,9 +73,7 @@ public class PartTreeJpaQueryIntegrationTests { @Test public void test() throws Exception { - Method method = UserRepository.class.getMethod("findByFirstname", String.class, Pageable.class); - JpaQueryMethod queryMethod = new JpaQueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class), - PersistenceProvider.fromEntityManager(entityManager)); + JpaQueryMethod queryMethod = getQueryMethod("findByFirstname", String.class, Pageable.class); PartTreeJpaQuery jpaQuery = new PartTreeJpaQuery(queryMethod, entityManager); jpaQuery.createQuery(new Object[] { "Matthews", new PageRequest(0, 1) }); @@ -101,9 +100,7 @@ public class PartTreeJpaQueryIntegrationTests { @Test public void recreatesQueryIfNullValueIsGiven() throws Exception { - Method method = UserRepository.class.getMethod("findByFirstname", String.class, Pageable.class); - JpaQueryMethod queryMethod = new JpaQueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class), - PersistenceProvider.fromEntityManager(entityManager)); + JpaQueryMethod queryMethod = getQueryMethod("findByFirstname", String.class, Pageable.class); PartTreeJpaQuery jpaQuery = new PartTreeJpaQuery(queryMethod, entityManager); Query query = jpaQuery.createQuery(new Object[] { "Matthews", new PageRequest(0, 1) }); @@ -124,12 +121,18 @@ public class PartTreeJpaQueryIntegrationTests { parameterTypes[i] = values[i].getClass(); } Method method = UserRepository.class.getMethod(methodName, parameterTypes); - JpaQueryMethod queryMethod = new JpaQueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class), - PersistenceProvider.fromEntityManager(entityManager)); + JpaQueryMethod queryMethod = getQueryMethod(methodName, parameterTypes); PartTreeJpaQuery jpaQuery = new PartTreeJpaQuery(queryMethod, entityManager); + jpaQuery.createQuery(values); } + private JpaQueryMethod getQueryMethod(String methodName, Class... parameterTypes) throws Exception { + Method method = UserRepository.class.getMethod(methodName, parameterTypes); + return new JpaQueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class), + new SpelAwareProxyProjectionFactory(), PersistenceProvider.fromEntityManager(entityManager)); + } + @SuppressWarnings("unchecked") private static T getValue(Object source, String path) { diff --git a/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java index c42ef877d..54851930f 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java @@ -43,6 +43,8 @@ import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.jpa.provider.QueryExtractor; import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.sample.UserRepository; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.query.EvaluationContextProvider; import org.springframework.data.repository.query.ExtensionAwareEvaluationContextProvider; @@ -72,6 +74,8 @@ public class SimpleJpaQueryUnitTests { @Mock RepositoryMetadata metadata; @Mock ParameterBinder binder; + ProjectionFactory factory = new SpelAwareProxyProjectionFactory(); + public @Rule ExpectedException exception = ExpectedException.none(); @Before @@ -86,14 +90,14 @@ public class SimpleJpaQueryUnitTests { when(metadata.getReturnedDomainClass(Mockito.any(Method.class))).thenReturn((Class) User.class); Method setUp = UserRepository.class.getMethod("findByLastname", String.class); - method = new JpaQueryMethod(setUp, metadata, extractor); + method = new JpaQueryMethod(setUp, metadata, factory, extractor); } @Test public void prefersDeclaredCountQueryOverCreatingOne() throws Exception { method = new JpaQueryMethod(SimpleJpaQueryUnitTests.class.getMethod("prefersDeclaredCountQueryOverCreatingOne"), - metadata, extractor); + metadata, factory, extractor); when(em.createQuery("foo", Long.class)).thenReturn(typedQuery); SimpleJpaQuery jpaQuery = new SimpleJpaQuery(method, em, "select u from User u", EVALUATION_CONTEXT_PROVIDER, @@ -111,7 +115,7 @@ public class SimpleJpaQueryUnitTests { when(em.createQuery(Mockito.anyString())).thenReturn(query); Method method = UserRepository.class.getMethod("findAllPaged", Pageable.class); - JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, extractor); + JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, factory, extractor); AbstractJpaQuery jpaQuery = new SimpleJpaQuery(queryMethod, em, "select u from User u", EVALUATION_CONTEXT_PROVIDER, PARSER); @@ -126,7 +130,7 @@ public class SimpleJpaQueryUnitTests { public void discoversNativeQuery() throws Exception { Method method = SampleRepository.class.getMethod("findNativeByLastname", String.class); - JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, extractor); + JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, factory, extractor); AbstractJpaQuery jpaQuery = JpaQueryFactory.INSTANCE.fromQueryAnnotation(queryMethod, em, EVALUATION_CONTEXT_PROVIDER); @@ -224,7 +228,7 @@ public class SimpleJpaQueryUnitTests { private AbstractJpaQuery createJpaQuery(Method method) { - JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, extractor); + JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, factory, extractor); return JpaQueryFactory.INSTANCE.fromQueryAnnotation(queryMethod, em, EVALUATION_CONTEXT_PROVIDER); }