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.
This commit is contained in:
Oliver Gierke
2017-09-28 14:39:20 +02:00
parent 90d26e0139
commit 40d1078934
4 changed files with 61 additions and 10 deletions

View File

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

View File

@@ -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;
}
}

View File

@@ -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<User> executeSpecWithSort(Sort sort) {
flushTestUsers();

View File

@@ -472,10 +472,20 @@ public interface UserRepository
List<RolesAndFirstname> 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<Role> getRoles();
}
interface NameOnly {
String getFirstname();
String getLastname();
}
}