From 6098d0182572fc0a6ecfb5bc16247d5d158d8252 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 | 16 ++++++--- .../data/jpa/repository/query/NamedQuery.java | 36 +++++++++++++++++-- .../jpa/repository/query/NativeJpaQuery.java | 16 +++------ ...ctStringBasedJpaQueryIntegrationTests.java | 2 +- 5 files changed, 50 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 a19cdfb8d..e813e5d9a 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 @@ -236,15 +236,12 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { * @return * @since 2.0.5 */ - protected Class getTypeToRead() { + protected Class getTypeToRead(ReturnedType returnedType) { if (PersistenceProvider.ECLIPSELINK.equals(provider)) { return null; } - ResultProcessor resultFactory = getQueryMethod().getResultProcessor(); - ReturnedType returnedType = resultFactory.getReturnedType(); - return returnedType.isProjecting() && !getMetamodel().isJpaManaged(returnedType.getReturnedType()) // ? HibernateUtils.isVersionOrBetter(HIBERNATE_VERSION_SUPPORTING_TUPLES) ? Tuple.class : null // : null; 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 94eba6a92..f31a1eb59 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 @@ -21,6 +21,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; @@ -72,8 +74,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()); return createBinder(values).bindAndPrepare(query); } @@ -98,8 +101,11 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery { String queryString = countQuery.getQueryString(); EntityManager em = getEntityManager(); - return createBinder(values).bind( - getQueryMethod().isNativeQuery() ? em.createNativeQuery(queryString) : em.createQuery(queryString, Long.class)); + Query query = getQueryMethod().isNativeQuery() // + ? em.createNativeQuery(queryString) // + : em.createQuery(queryString, Long.class); + + return createBinder(values).bind(query); } /** @@ -123,7 +129,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery { * @param queryString * @return */ - protected Query createJpaQuery(String queryString) { + protected Query createJpaQuery(String queryString, ReturnedType returnedType) { EntityManager em = getEntityManager(); @@ -131,7 +137,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery { return em.createQuery(queryString); } - Class typeToRead = getTypeToRead(); + Class typeToRead = getTypeToRead(returnedType); return typeToRead == null ? em.createQuery(queryString) : em.createQuery(queryString, typeToRead); } 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 a6465adb3..ac2f83030 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 @@ -17,14 +17,18 @@ package org.springframework.data.jpa.repository.query; 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.util.StringUtils; /** @@ -147,7 +151,11 @@ final class NamedQuery extends AbstractJpaQuery { EntityManager em = getEntityManager(); - Class typeToRead = getTypeToRead(); + JpaQueryMethod queryMethod = getQueryMethod(); + ResultProcessor processor = queryMethod.getResultProcessor() + .withDynamicProjection(new ParametersParameterAccessor(queryMethod.getParameters(), values)); + + Class typeToRead = getTypeToRead(processor.getReturnedType()); Query query = typeToRead == null ? em.createNamedQuery(queryName) : em.createNamedQuery(queryName, typeToRead); return createBinder(values).bindAndPrepare(query); @@ -186,7 +194,29 @@ final class NamedQuery extends AbstractJpaQuery { * @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#getTypeToRead() */ @Override - protected Class getTypeToRead() { - return declaredQuery != null && !declaredQuery.hasConstructorExpression() ? super.getTypeToRead() : null; + protected Class 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 type; + } + + // Domain type supertype -> use domain type + if (type.isAssignableFrom(domainType)) { + return domainType; + } + + // Tuples for projection interfaces or explicit SQL mappings for everything else + return type.isInterface() ? Tuple.class : null; + } + + return declaredQuery != null && !declaredQuery.hasConstructorExpression() // + ? super.getTypeToRead(returnedType) // + : null; } } 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 d312ed970..b5b10dc9e 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 org.springframework.data.jpa.provider.HibernateUtils; 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; @@ -37,8 +36,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}. * @@ -61,8 +58,6 @@ final class NativeJpaQuery extends AbstractStringBasedJpaQuery { throw new InvalidJpaQueryMethodException( "Cannot use native queries with dynamic sorting and/or pagination in method " + method); } - - this.resultType = getTypeToQueryFor(); } /* @@ -70,18 +65,15 @@ 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); } - 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 2a9cbcfec..3f8bdfcea 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));