From 40d1078934bc6c9ccc47c2c86855df9463827820 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 28 Sep 2017 14:39:20 +0200 Subject: [PATCH] DATAJPA-980 - Native query execution now also uses a Tuple query for projections. If a projection interface is used as return type for a native query we now trigger a tuple query. Note that this requires Hibernate 5.2.11. --- .../query/AbstractStringBasedJpaQuery.java | 7 ++--- .../jpa/repository/query/NativeJpaQuery.java | 30 +++++++++++++++++-- .../jpa/repository/UserRepositoryTests.java | 22 ++++++++++++-- .../jpa/repository/sample/UserRepository.java | 12 +++++++- 4 files changed, 61 insertions(+), 10 deletions(-) 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 7a0392edb..a737193c6 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 @@ -137,9 +137,8 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery { ResultProcessor resultFactory = getQueryMethod().getResultProcessor(); ReturnedType returnedType = resultFactory.getReturnedType(); - getMetamodel().isJpaManaged(returnedType.getReturnedType()); - - return returnedType.isProjecting() && !getMetamodel().isJpaManaged(returnedType.getReturnedType()) - ? em.createQuery(queryString, Tuple.class) : em.createQuery(queryString); + return returnedType.isProjecting() && !getMetamodel().isJpaManaged(returnedType.getReturnedType()) // + ? em.createQuery(queryString, Tuple.class) // + : em.createQuery(queryString); } } 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 46f08fdac..d025d4a0d 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 @@ -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.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; /** @@ -33,6 +36,8 @@ 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}. * @@ -55,6 +60,8 @@ final class NativeJpaQuery extends AbstractStringBasedJpaQuery { throw new InvalidJpaQueryMethodException( "Cannot use native queries with dynamic sorting and/or pagination in method " + method); } + + this.resultType = getTypeToQueryFor(); } /* @@ -63,8 +70,25 @@ final class NativeJpaQuery extends AbstractStringBasedJpaQuery { */ @Override protected Query createJpaQuery(String queryString) { - return getQueryMethod().isQueryForEntity() - ? getEntityManager().createNativeQuery(queryString, getQueryMethod().getReturnedObjectType()) - : getEntityManager().createNativeQuery(queryString); + + EntityManager em = getEntityManager(); + + return this.resultType == null ? em.createNativeQuery(queryString) + : em.createNativeQuery(queryString, this.resultType); + } + + private Class getTypeToQueryFor() { + + ResultProcessor resultFactory = getQueryMethod().getResultProcessor(); + ReturnedType returnedType = resultFactory.getReturnedType(); + + Class result = getQueryMethod().isQueryForEntity() ? returnedType.getDomainType() : null; + + if (this.getQuery().hasConstructorExpression() || this.getQuery().isDefaultProjection()) { + return result; + } + + return returnedType.isProjecting() && !getMetamodel().isJpaManaged(returnedType.getReturnedType()) ? Tuple.class + : result; } } 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 d16d9f012..29e0715fa 100644 --- a/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -21,8 +21,8 @@ import static org.junit.Assert.*; import static org.springframework.data.domain.Example.*; import static org.springframework.data.domain.ExampleMatcher.*; import static org.springframework.data.domain.Sort.Direction.*; -import static org.springframework.data.jpa.domain.Specifications.not; import static org.springframework.data.jpa.domain.Specifications.*; +import static org.springframework.data.jpa.domain.Specifications.not; import static org.springframework.data.jpa.domain.sample.UserSpecifications.*; import java.util.ArrayList; @@ -44,6 +44,7 @@ import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import org.hamcrest.Matchers; +import org.hibernate.Version; import org.junit.Assume; import org.junit.Before; import org.junit.Ignore; @@ -55,7 +56,8 @@ import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; -import org.springframework.data.domain.ExampleMatcher.*; +import org.springframework.data.domain.ExampleMatcher.GenericPropertyMatcher; +import org.springframework.data.domain.ExampleMatcher.StringMatcher; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; @@ -72,6 +74,7 @@ import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.jpa.repository.sample.SampleEvaluationContextExtension.SampleSecurityContextHolder; import org.springframework.data.jpa.repository.sample.UserRepository; +import org.springframework.data.jpa.repository.sample.UserRepository.NameOnly; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; @@ -2155,6 +2158,21 @@ public class UserRepositoryTests { assertThat(users, hasSize(1)); } + @Test // DATAJPA-980 + public void supportsProjectionsWithNativeQueries() { + + Assume.assumeTrue(Version.getVersionString().startsWith("5.2")); + + flushTestUsers(); + + User user = repository.findAll().get(0); + + NameOnly result = repository.findByNativeQuery(user.getId()); + + assertThat(result.getFirstname(), is(user.getFirstname())); + assertThat(result.getLastname(), is(user.getLastname())); + } + 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 f55fe04c2..7bc732150 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 @@ -472,10 +472,20 @@ public interface UserRepository List findRolesAndFirstnameBy(); - static interface RolesAndFirstname { + @Query(value = "SELECT firstname, lastname from SD_User WHERE id = ?1", nativeQuery = true) + NameOnly findByNativeQuery(Integer id); + + interface RolesAndFirstname { String getFirstname(); Set getRoles(); } + + interface NameOnly { + + String getFirstname(); + + String getLastname(); + } }