DATAJPA-864 - Projection execution now considers constructor expressions in manually defined queries.

Previously we triggered a tuple query execution even if a query was manually defined and contained a constructor expression (e.g. new Dto(a.foo, a.bar)). We now explicitly detect that case and simply execute the query as is.
This commit is contained in:
Oliver Gierke
2016-02-15 13:15:43 +01:00
parent e25ab1f721
commit 86d50bee2e
5 changed files with 91 additions and 32 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2014 the original author or authors.
* Copyright 2008-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -91,22 +91,6 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
evaluationContextProvider, parser);
}
/**
* Creates an appropriate JPA query from an {@link EntityManager} according to the current {@link AbstractJpaQuery}
* type.
*
* @param queryString
* @return
*/
public Query createJpaQuery(String queryString) {
ResultProcessor resultFactory = getQueryMethod().getResultProcessor();
ReturnedType returnedType = resultFactory.getReturnedType();
EntityManager em = getEntityManager();
return returnedType.isProjecting() ? em.createQuery(queryString, Tuple.class) : em.createQuery(queryString);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#doCreateCountQuery(java.lang.Object[])
@@ -134,4 +118,25 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
public StringQuery getCountQuery() {
return countQuery;
}
/**
* Creates an appropriate JPA query from an {@link EntityManager} according to the current {@link AbstractJpaQuery}
* type.
*
* @param queryString
* @return
*/
protected Query createJpaQuery(String queryString) {
EntityManager em = getEntityManager();
if (this.query.hasConstructorExpression()) {
return em.createQuery(queryString);
}
ResultProcessor resultFactory = getQueryMethod().getResultProcessor();
ReturnedType returnedType = resultFactory.getReturnedType();
return returnedType.isProjecting() ? em.createQuery(queryString, Tuple.class) : em.createQuery(queryString);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
* {@link Query} from it.
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
final class NativeJpaQuery extends AbstractStringBasedJpaQuery {
@@ -61,8 +62,9 @@ final class NativeJpaQuery extends AbstractStringBasedJpaQuery {
* @see org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery#createJpaQuery(java.lang.String)
*/
@Override
public Query createJpaQuery(String queryString) {
return getQueryMethod().isQueryForEntity() ? getEntityManager().createNativeQuery(queryString,
getQueryMethod().getReturnedObjectType()) : getEntityManager().createNativeQuery(queryString);
protected Query createJpaQuery(String queryString) {
return getQueryMethod().isQueryForEntity()
? getEntityManager().createNativeQuery(queryString, getQueryMethod().getReturnedObjectType())
: getEntityManager().createNativeQuery(queryString);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2015 the original author or authors.
* Copyright 2008-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -93,6 +93,8 @@ public abstract class QueryUtils {
private static final Pattern NAMED_PARAMETER = Pattern.compile(":" + IDENTIFIER + "|\\#" + IDENTIFIER,
CASE_INSENSITIVE);
private static final Pattern CONSTRUCTOR_EXPRESSION;
private static final Map<PersistentAttributeType, Class<? extends Annotation>> ASSOCIATION_TYPES;
private static final int QUERY_JOIN_ALIAS_GROUP_INDEX = 2;
@@ -127,6 +129,19 @@ public abstract class QueryUtils {
persistentAttributeTypes.put(ELEMENT_COLLECTION, null);
ASSOCIATION_TYPES = Collections.unmodifiableMap(persistentAttributeTypes);
builder = new StringBuilder();
builder.append("select");
builder.append("\\s+"); // at least one space separating
builder.append("new");
builder.append("\\s+"); // at least one space separating
builder.append(IDENTIFIER);
builder.append("\\s*"); // zero to unlimited space separating
builder.append("\\(");
builder.append(".*");
builder.append("\\)");
CONSTRUCTOR_EXPRESSION = compile(builder.toString(), CASE_INSENSITIVE);
}
/**
@@ -430,6 +445,20 @@ public abstract class QueryUtils {
return orders;
}
/**
* Returns whether the given JPQL query contains a constructor expression.
*
* @param query must not be {@literal null} or empty.
* @return
* @since 1.10
*/
public static boolean hasConstructorExpression(String query) {
Assert.hasText(query, "Query must not be null or empty!");
return CONSTRUCTOR_EXPRESSION.matcher(query).find();
}
/**
* Creates a criteria API {@link javax.persistence.criteria.Order} from the given {@link Order}.
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors.
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,6 +45,7 @@ class StringQuery {
private final String query;
private final List<ParameterBinding> bindings;
private final String alias;
private final boolean hasConstructorExpression;
/**
* Creates a new {@link StringQuery} from the given JPQL query.
@@ -59,6 +60,7 @@ class StringQuery {
this.query = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query,
this.bindings);
this.alias = QueryUtils.detectAlias(query);
this.hasConstructorExpression = QueryUtils.hasConstructorExpression(query);
}
/**
@@ -133,6 +135,16 @@ class StringQuery {
throw new IllegalArgumentException(String.format("No parameter binding found for position %s!", position));
}
/**
* Returns whether the query is using a constructor expression.
*
* @return
* @since 1.10
*/
public boolean hasConstructorExpression() {
return hasConstructorExpression;
}
/**
* A parser that extracts the parameter bindings from a given query string.
*
@@ -328,8 +340,8 @@ class StringQuery {
}
/**
* Return the appropriate {@link ParameterBindingType} for the given {@link String}. Returns {@keyword
* #AS_IS} in case no other {@link ParameterBindingType} could be found.
* Return the appropriate {@link ParameterBindingType} for the given {@link String}. Returns {@keyword #AS_IS} in
* case no other {@link ParameterBindingType} could be found.
*
* @param typeSource
* @return

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors.
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -312,16 +312,16 @@ public class StringQueryUnitTests {
public void rejectsDifferentBindingsForRepeatedParameter2() {
new StringQuery("select u from User u where u.firstname like ?1 and u.lastname like %?1");
}
/**
* @see @DATAJPA-712
*/
@Test
public void shouldReplaceAllNamedExpressionParametersWithInClause() {
StringQuery query = new StringQuery("select a from A a where a.b in :#{#bs} and a.c in :#{#cs}");
String queryString = query.getQueryString();
assertThat(queryString, is("select a from A a where a.b in :__$synthetic$__1 and a.c in :__$synthetic$__2"));
}
@@ -330,13 +330,24 @@ public class StringQueryUnitTests {
*/
@Test
public void shouldReplaceAllPositionExpressionParametersWithInClause() {
StringQuery query = new StringQuery("select a from A a where a.b in ?#{#bs} and a.c in ?#{#cs}");
String queryString = query.getQueryString();
assertThat(queryString, is("select a from A a where a.b in ?1 and a.c in ?2"));
}
/**
* @see DATAJPA-864
*/
@Test
public void detectsConstructorExpressions() {
assertThat(new StringQuery("select new Dto(a.foo, a.bar) from A a").hasConstructorExpression(), is(true));
assertThat(new StringQuery("select new Dto (a.foo, a.bar) from A a").hasConstructorExpression(), is(true));
assertThat(new StringQuery("select a from A a").hasConstructorExpression(), is(false));
}
private void assertPositionalBinding(Class<? extends ParameterBinding> bindingType, Integer position,
ParameterBinding expectedBinding) {