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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user