From c9605c5eb8b8464f2dbea3218a85a5e780048e16 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 9 May 2018 15:42:18 +0200 Subject: [PATCH] DATAJPA-1280 - Tweaked the type handling for named native queries. We now only use Tuple as target type to execute a named native query if a projection interface, i.e. an interface that's not part of the entity type's type hierarchy is used as return type. If a type within the entity types hierarchy is returned we use the returned type as type hint. In all other cases (usually DTOs) we rely on the named native query being backed by a custom JPA 2.1 SQL result set mapping. --- .../repository/query/AbstractJpaQuery.java | 5 +-- .../query/AbstractStringBasedJpaQuery.java | 9 +++-- .../data/jpa/repository/query/NamedQuery.java | 36 +++++++++++++++++-- .../jpa/repository/query/NativeJpaQuery.java | 19 +++------- ...ctStringBasedJpaQueryIntegrationTests.java | 2 +- 5 files changed, 46 insertions(+), 25 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 dff0e6c03..f55ca77df 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 @@ -244,15 +244,12 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { * @return * @since 2.0.5 */ - protected Optional> getTypeToRead() { + protected Optional> getTypeToRead(ReturnedType returnedType) { if (PersistenceProvider.ECLIPSELINK.equals(provider)) { return Optional.empty(); } - ResultProcessor resultFactory = getQueryMethod().getResultProcessor(); - ReturnedType returnedType = resultFactory.getReturnedType(); - return returnedType.isProjecting() && !getMetamodel().isJpaManaged(returnedType.getReturnedType()) ? Optional.of(Tuple.class) : Optional.empty(); 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 03db33da2..b457c33a1 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 @@ -23,6 +23,8 @@ import javax.persistence.Query; 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; @@ -78,8 +80,9 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery { ParameterAccessor accessor = new ParametersParameterAccessor(getQueryMethod().getParameters(), values); String sortedQueryString = QueryUtils.applySorting(query.getQueryString(), accessor.getSort(), query.getAlias()); + ResultProcessor processor = getQueryMethod().getResultProcessor().withDynamicProjection(accessor); - Query query = createJpaQuery(sortedQueryString); + Query query = createJpaQuery(sortedQueryString, processor.getReturnedType()); // it is ok to reuse the binding contained in the ParameterBinder although we create a new query String because the // parameters in the query do not change. @@ -132,7 +135,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery { * Creates an appropriate JPA query from an {@link EntityManager} according to the current {@link AbstractJpaQuery} * type. */ - protected Query createJpaQuery(String queryString) { + protected Query createJpaQuery(String queryString, ReturnedType returnedType) { EntityManager em = getEntityManager(); @@ -140,7 +143,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery { return em.createQuery(queryString); } - return getTypeToRead() // + return getTypeToRead(returnedType) // . map(it -> em.createQuery(queryString, it)) // .orElseGet(() -> em.createQuery(queryString)); } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java index 91745256c..762cb3609 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java @@ -21,14 +21,18 @@ import java.util.Optional; import javax.persistence.EntityManager; import javax.persistence.Query; +import javax.persistence.Tuple; import javax.persistence.TypedQuery; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.jpa.provider.QueryExtractor; import org.springframework.data.repository.query.Parameters; +import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.data.repository.query.QueryCreationException; import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.query.ResultProcessor; +import org.springframework.data.repository.query.ReturnedType; import org.springframework.lang.Nullable; /** @@ -155,7 +159,11 @@ final class NamedQuery extends AbstractJpaQuery { EntityManager em = getEntityManager(); - Query query = getTypeToRead() // + JpaQueryMethod queryMethod = getQueryMethod(); + ResultProcessor processor = queryMethod.getResultProcessor() + .withDynamicProjection(new ParametersParameterAccessor(queryMethod.getParameters(), values)); + + Query query = getTypeToRead(processor.getReturnedType()) // . map(it -> em.createNamedQuery(queryName, it)) // .orElseGet(() -> em.createNamedQuery(queryName)); @@ -191,7 +199,29 @@ final class NamedQuery extends AbstractJpaQuery { * @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#getTypeToRead() */ @Override - protected Optional> getTypeToRead() { - return declaredQuery.hasConstructorExpression() ? Optional.empty() : super.getTypeToRead(); + protected Optional> getTypeToRead(ReturnedType returnedType) { + + if (getQueryMethod().isNativeQuery()) { + + Class type = returnedType.getReturnedType(); + Class domainType = returnedType.getDomainType(); + + // Domain or subtype -> use return type + if (domainType.isAssignableFrom(type)) { + return Optional.of(type); + } + + // Domain type supertype -> use domain type + if (type.isAssignableFrom(domainType)) { + return Optional.of(domainType); + } + + // Tuples for projection interfaces or explicit SQL mappings for everything else + return type.isInterface() ? Optional.of(Tuple.class) : Optional.empty(); + } + + return declaredQuery.hasConstructorExpression() // + ? Optional.empty() // + : super.getTypeToRead(returnedType); } } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java index ab89dc540..282ac20da 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java @@ -23,7 +23,6 @@ import javax.persistence.Tuple; import org.springframework.data.repository.query.EvaluationContextProvider; import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.RepositoryQuery; -import org.springframework.data.repository.query.ResultProcessor; import org.springframework.data.repository.query.ReturnedType; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -38,8 +37,6 @@ import org.springframework.expression.spel.standard.SpelExpressionParser; */ final class NativeJpaQuery extends AbstractStringBasedJpaQuery { - private final Class resultType; - /** * Creates a new {@link NativeJpaQuery} encapsulating the query annotated on the given {@link JpaQueryMethod}. * @@ -56,11 +53,8 @@ final class NativeJpaQuery extends AbstractStringBasedJpaQuery { Parameters parameters = method.getParameters(); if (parameters.hasSortParameter() && !queryString.contains("#sort")) { - throw new InvalidJpaQueryMethodException( - "Cannot use native queries with dynamic sorting in method " + method); + throw new InvalidJpaQueryMethodException("Cannot use native queries with dynamic sorting in method " + method); } - - this.resultType = getTypeToQueryFor(); } /* @@ -68,19 +62,16 @@ final class NativeJpaQuery extends AbstractStringBasedJpaQuery { * @see org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery#createJpaQuery(java.lang.String) */ @Override - protected Query createJpaQuery(String queryString) { + protected Query createJpaQuery(String queryString, ReturnedType returnedType) { EntityManager em = getEntityManager(); + Class type = getTypeToQueryFor(returnedType); - return this.resultType == null ? em.createNativeQuery(queryString) - : em.createNativeQuery(queryString, this.resultType); + return type == null ? em.createNativeQuery(queryString) : em.createNativeQuery(queryString, type); } @Nullable - private Class getTypeToQueryFor() { - - ResultProcessor resultFactory = getQueryMethod().getResultProcessor(); - ReturnedType returnedType = resultFactory.getReturnedType(); + private Class getTypeToQueryFor(ReturnedType returnedType) { Class result = getQueryMethod().isQueryForEntity() ? returnedType.getDomainType() : null; diff --git a/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java index aeb09742b..46b5ed9fd 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java @@ -62,7 +62,7 @@ public class AbstractStringBasedJpaQueryIntegrationTests { AbstractStringBasedJpaQuery jpaQuery = new SimpleJpaQuery(method, mock, DefaultEvaluationContextProvider.INSTANCE, new SpelExpressionParser()); - jpaQuery.createJpaQuery(method.getAnnotatedQuery()); + jpaQuery.createJpaQuery(method.getAnnotatedQuery(), method.getResultProcessor().getReturnedType()); verify(mock, times(1)).createQuery(anyString()); verify(mock, times(0)).createQuery(anyString(), eq(Tuple.class));