From 96a05d4be94debff2060ee079414aae04861fe04 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 7 Jun 2016 18:22:44 +0200 Subject: [PATCH] DATAJPA-885 - Make sure manually defined queries returning domain type don't use tuples. We now check manually defined queries for whether the projection is equal to the used alias and don't use a Tuple query in this case. This allows the manually defined queries to still define the projections manually but also the returned objects be wrapped with projection interfaces easily. --- .../query/AbstractStringBasedJpaQuery.java | 2 +- .../data/jpa/repository/query/QueryUtils.java | 16 ++++++++++++ .../jpa/repository/query/StringQuery.java | 9 +++++++ .../query/SimpleJpaQueryUnitTests.java | 25 ++++++++++++++++++- 4 files changed, 50 insertions(+), 2 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 55933a649..2972a3992 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 @@ -131,7 +131,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery { EntityManager em = getEntityManager(); - if (this.query.hasConstructorExpression()) { + if (this.query.hasConstructorExpression() || this.query.isDefaultProjection()) { return em.createQuery(queryString); } diff --git a/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java b/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java index 298356184..328958bab 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java @@ -80,6 +80,7 @@ public abstract class QueryUtils { private static final Pattern ALIAS_MATCH; private static final Pattern COUNT_MATCH; + private static final Pattern PROJECTION_CLAUSE = Pattern.compile("select\\s+(.+)\\s+from"); private static final Pattern NO_DIGITS = Pattern.compile("\\D+"); private static final String IDENTIFIER = "[\\p{Lu}\\P{InBASIC_LATIN}\\p{Alnum}._$]+"; @@ -464,6 +465,21 @@ public abstract class QueryUtils { return CONSTRUCTOR_EXPRESSION.matcher(query).find(); } + /** + * Returns the projection part of the query, i.e. everything between {@code select} and {@code from}. + * + * @param query must not be {@literal null} or empty. + * @return + * @since 1.10.2 + */ + public static String getProjection(String query) { + + Assert.hasText(query, "Query must not be null or empty!"); + + Matcher matcher = PROJECTION_CLAUSE.matcher(query); + return matcher.find() ? matcher.group(1) : ""; + } + /** * Creates a criteria API {@link javax.persistence.criteria.Order} from the given {@link Order}. * diff --git a/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java index e1c3f5142..bc713ae95 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java @@ -145,6 +145,15 @@ class StringQuery { return hasConstructorExpression; } + /** + * Returns whether the query uses the default projection, i.e. returns the main alias defined for the query. + * + * @return + */ + public boolean isDefaultProjection() { + return QueryUtils.getProjection(query).equals(alias); + } + /** * A parser that extracts the parameter bindings from a given query string. * diff --git a/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java index 54851930f..f84d67252 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java @@ -21,10 +21,12 @@ import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; import java.lang.reflect.Method; +import java.util.Collection; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; +import javax.persistence.Tuple; import javax.persistence.TypedQuery; import org.junit.Before; @@ -223,7 +225,23 @@ public class SimpleJpaQueryUnitTests { jpaQuery.doCreateCountQuery(new Object[] { new PageRequest(0, 10) }); - verify(em, times(1)).createNativeQuery(anyString()); + verify(em).createNativeQuery(anyString()); + } + + /** + * @see DATAJPA-885 + */ + @Test + public void projectsWithManuallyDeclaredQuery() throws Exception { + + AbstractJpaQuery jpaQuery = createJpaQuery(SampleRepository.class.getMethod("projectWithExplicitQuery")); + + jpaQuery.createQuery(new Object[0]); + + verify(em, times(0)).createQuery(anyString(), eq(Tuple.class)); + + // Two times, first one is from the query validation + verify(em, times(2)).createQuery(anyString()); } private AbstractJpaQuery createJpaQuery(Method method) { @@ -248,5 +266,10 @@ public class SimpleJpaQueryUnitTests { @Query(USER_QUERY) Page pageByAnnotatedQuery(Pageable pageable); + + @Query("select u from User u") + Collection projectWithExplicitQuery(); } + + interface UserProjection {} }