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));