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 d8ea06fa3..31476f6e2 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 @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -234,6 +235,22 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { return method.applyHintsToCountQuery() ? applyHints(countQuery, method) : countQuery; } + /** + * Returns the type to be used when creating the JPA query. + * + * @return + * @since 2.0.5 + */ + protected Optional> getTypeToRead() { + + ResultProcessor resultFactory = getQueryMethod().getResultProcessor(); + ReturnedType returnedType = resultFactory.getReturnedType(); + + return returnedType.isProjecting() && !getMetamodel().isJpaManaged(returnedType.getReturnedType()) + ? Optional.of(Tuple.class) + : Optional.empty(); + } + /** * Creates a {@link Query} instance for the given values. * 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 48bf26e6d..0993f0723 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 @@ -19,13 +19,10 @@ import static org.springframework.data.jpa.repository.query.QueryParameterSetter 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; @@ -140,11 +137,8 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery { return em.createQuery(queryString); } - ResultProcessor resultFactory = getQueryMethod().getResultProcessor(); - ReturnedType returnedType = resultFactory.getReturnedType(); - - return returnedType.isProjecting() && !getMetamodel().isJpaManaged(returnedType.getReturnedType()) - ? em.createQuery(queryString, Tuple.class) - : em.createQuery(queryString); + return getTypeToRead() // + . 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 ecfde50a9..d437b8f36 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 @@ -145,7 +145,12 @@ final class NamedQuery extends AbstractJpaQuery { @Override protected Query doCreateQuery(Object[] values) { - Query query = getEntityManager().createNamedQuery(queryName); + EntityManager em = getEntityManager(); + + Query query = getTypeToRead() // + . map(it -> em.createNamedQuery(queryName, it)) // + .orElseGet(() -> em.createNamedQuery(queryName)); + return parameterBinder.get().bindAndPrepare(query, values); } diff --git a/src/test/java/org/springframework/data/jpa/domain/sample/User.java b/src/test/java/org/springframework/data/jpa/domain/sample/User.java index 33ea83ae4..90b63f481 100644 --- a/src/test/java/org/springframework/data/jpa/domain/sample/User.java +++ b/src/test/java/org/springframework/data/jpa/domain/sample/User.java @@ -37,6 +37,7 @@ import javax.persistence.NamedEntityGraph; import javax.persistence.NamedEntityGraphs; import javax.persistence.NamedNativeQueries; import javax.persistence.NamedNativeQuery; +import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.NamedStoredProcedureQueries; import javax.persistence.NamedStoredProcedureQuery; @@ -80,7 +81,11 @@ import javax.persistence.TemporalType; @NamedAttributeNode(value = "colleagues", subgraph = "User.colleaguesOfColleagues") }), @NamedSubgraph(name = "User.colleaguesOfColleagues", attributeNodes = { @NamedAttributeNode("roles"), }) }) }) -@NamedQuery(name = "User.findByEmailAddress", query = "SELECT u FROM User u WHERE u.emailAddress = ?1") +@NamedQueries({ // + @NamedQuery(name = "User.findByEmailAddress", // + query = "SELECT u FROM User u WHERE u.emailAddress = ?1"), // + @NamedQuery(name = "User.findByNamedQueryWithAliasInInvertedOrder", // + query = "SELECT u.lastname AS lastname, u.firstname AS firstname FROM User u ORDER BY u.lastname ASC") }) @NamedStoredProcedureQueries({ // @NamedStoredProcedureQuery(name = "User.plus1", procedureName = "plus1inout", parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, name = "arg", type = Integer.class), 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 18bceb8ce..f3e35c4f5 100644 --- a/src/test/java/org/springframework/data/jpa/infrastructure/EclipseLinkMetamodelIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/infrastructure/EclipseLinkMetamodelIntegrationTests.java @@ -50,4 +50,12 @@ public class EclipseLinkMetamodelIntegrationTests extends MetamodelIntegrationTe @Ignore @Override public void doesNotExposeAliasForTupleIfNoneDefined() {} + + /** + * TODO: Remove, once https://bugs.eclipse.org/bugs/show_bug.cgi?id=289141 is fixed. + */ + @Test + @Ignore + @Override + public void returnsAliasesInTuple() {} } 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 ed868bcc8..37b7d8517 100644 --- a/src/test/java/org/springframework/data/jpa/infrastructure/MetamodelIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/infrastructure/MetamodelIntegrationTests.java @@ -15,8 +15,9 @@ */ package org.springframework.data.jpa.infrastructure; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.junit.Assert.assertThat; import java.util.List; @@ -86,6 +87,7 @@ public abstract class MetamodelIntegrationTests { public void doesNotExposeAliasForTupleIfNoneDefined() { User user = new User(); + user.setFirstname("Dave"); user.setEmailAddress("email"); @@ -99,4 +101,25 @@ public abstract class MetamodelIntegrationTests { assertThat(elements, hasSize(1)); assertThat(elements.get(0).getAlias(), is(nullValue())); } + + @Test + @Transactional + public void returnsAliasesInTuple() { + + User user = new User(); + user.setFirstname("Dave"); + user.setLastname("Matthews"); + user.setEmailAddress("email"); + + em.persist(user); + + TypedQuery query = em.createQuery( + "SELECT u.lastname AS lastname, u.firstname AS firstname FROM User u ORDER BY u.lastname ASC", Tuple.class); + + List resultList = query.getResultList(); + List> elements = resultList.get(0).getElements(); + + assertThat(elements).hasSize(2); + assertThat(elements).extracting(TupleElement::getAlias).contains("firstname", "lastname"); + } } diff --git a/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java index 013ba7be6..50a758ec2 100644 --- a/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/EclipseLinkNamespaceUserRepositoryTests.java @@ -101,7 +101,13 @@ public class EclipseLinkNamespaceUserRepositoryTests extends NamespaceUserReposi @Ignore @Override @Test // DATAJPA-1248 - public void supportsProjectionsWithNativeQueriesAndCamelCaseProperty() { - super.supportsProjectionsWithNativeQueriesAndCamelCaseProperty(); - } + public void supportsProjectionsWithNativeQueriesAndCamelCaseProperty() {} + + /** + * TODO: Remove, once https://bugs.eclipse.org/bugs/show_bug.cgi?id=289141 is fixed. + */ + @Ignore + @Override + @Test + public void bindsNativeQueryResultsToProjectionByName() {} } diff --git a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index 938096730..ef68aada5 100644 --- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -2160,6 +2160,19 @@ public class UserRepositoryTests { softly.assertAll(); } + @Test // DATAJPA-1273 + public void bindsNativeQueryResultsToProjectionByName() { + + flushTestUsers(); + + List result = repository.findByNamedQueryWithAliasInInvertedOrder(); + + assertThat(result).element(0).satisfies(it -> { + assertThat(it.getFirstname()).isEqualTo("Joachim"); + assertThat(it.getLastname()).isEqualTo("Arrasz"); + }); + } + private Page executeSpecWithSort(Sort sort) { flushTestUsers(); diff --git a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index 63799229c..843708fcf 100644 --- a/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -536,6 +536,9 @@ public interface UserRepository @Query(value = "SELECT firstname FROM SD_User ORDER BY UCASE(firstname)", countQuery = "SELECT count(*) FROM SD_User", nativeQuery = true) Page findByNativeQueryWithPageable(@Param("pageable") Pageable pageable); + // DATAJPA-1273 + List findByNamedQueryWithAliasInInvertedOrder(); + interface RolesAndFirstname { String getFirstname();