Skip to content

Commit 86d50be

Browse files
committed
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.
1 parent e25ab1f commit 86d50be

File tree

5 files changed

+91
-32
lines changed

5 files changed

+91
-32
lines changed

src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2008-2014 the original author or authors.
2+
* Copyright 2008-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -91,22 +91,6 @@ protected ParameterBinder createBinder(Object[] values) {
9191
evaluationContextProvider, parser);
9292
}
9393

94-
/**
95-
* Creates an appropriate JPA query from an {@link EntityManager} according to the current {@link AbstractJpaQuery}
96-
* type.
97-
*
98-
* @param queryString
99-
* @return
100-
*/
101-
public Query createJpaQuery(String queryString) {
102-
103-
ResultProcessor resultFactory = getQueryMethod().getResultProcessor();
104-
ReturnedType returnedType = resultFactory.getReturnedType();
105-
EntityManager em = getEntityManager();
106-
107-
return returnedType.isProjecting() ? em.createQuery(queryString, Tuple.class) : em.createQuery(queryString);
108-
}
109-
11094
/*
11195
* (non-Javadoc)
11296
* @see org.springframework.data.jpa.repository.query.AbstractJpaQuery#doCreateCountQuery(java.lang.Object[])
@@ -134,4 +118,25 @@ public StringQuery getQuery() {
134118
public StringQuery getCountQuery() {
135119
return countQuery;
136120
}
121+
122+
/**
123+
* Creates an appropriate JPA query from an {@link EntityManager} according to the current {@link AbstractJpaQuery}
124+
* type.
125+
*
126+
* @param queryString
127+
* @return
128+
*/
129+
protected Query createJpaQuery(String queryString) {
130+
131+
EntityManager em = getEntityManager();
132+
133+
if (this.query.hasConstructorExpression()) {
134+
return em.createQuery(queryString);
135+
}
136+
137+
ResultProcessor resultFactory = getQueryMethod().getResultProcessor();
138+
ReturnedType returnedType = resultFactory.getReturnedType();
139+
140+
return returnedType.isProjecting() ? em.createQuery(queryString, Tuple.class) : em.createQuery(queryString);
141+
}
137142
}

src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2014 the original author or authors.
2+
* Copyright 2013-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@
2929
* {@link Query} from it.
3030
*
3131
* @author Thomas Darimont
32+
* @author Oliver Gierke
3233
*/
3334
final class NativeJpaQuery extends AbstractStringBasedJpaQuery {
3435

@@ -61,8 +62,9 @@ public NativeJpaQuery(JpaQueryMethod method, EntityManager em, String queryStrin
6162
* @see org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery#createJpaQuery(java.lang.String)
6263
*/
6364
@Override
64-
public Query createJpaQuery(String queryString) {
65-
return getQueryMethod().isQueryForEntity() ? getEntityManager().createNativeQuery(queryString,
66-
getQueryMethod().getReturnedObjectType()) : getEntityManager().createNativeQuery(queryString);
65+
protected Query createJpaQuery(String queryString) {
66+
return getQueryMethod().isQueryForEntity()
67+
? getEntityManager().createNativeQuery(queryString, getQueryMethod().getReturnedObjectType())
68+
: getEntityManager().createNativeQuery(queryString);
6769
}
6870
}

src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2008-2015 the original author or authors.
2+
* Copyright 2008-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -93,6 +93,8 @@ public abstract class QueryUtils {
9393
private static final Pattern NAMED_PARAMETER = Pattern.compile(":" + IDENTIFIER + "|\\#" + IDENTIFIER,
9494
CASE_INSENSITIVE);
9595

96+
private static final Pattern CONSTRUCTOR_EXPRESSION;
97+
9698
private static final Map<PersistentAttributeType, Class<? extends Annotation>> ASSOCIATION_TYPES;
9799

98100
private static final int QUERY_JOIN_ALIAS_GROUP_INDEX = 2;
@@ -127,6 +129,19 @@ public abstract class QueryUtils {
127129
persistentAttributeTypes.put(ELEMENT_COLLECTION, null);
128130

129131
ASSOCIATION_TYPES = Collections.unmodifiableMap(persistentAttributeTypes);
132+
133+
builder = new StringBuilder();
134+
builder.append("select");
135+
builder.append("\\s+"); // at least one space separating
136+
builder.append("new");
137+
builder.append("\\s+"); // at least one space separating
138+
builder.append(IDENTIFIER);
139+
builder.append("\\s*"); // zero to unlimited space separating
140+
builder.append("\\(");
141+
builder.append(".*");
142+
builder.append("\\)");
143+
144+
CONSTRUCTOR_EXPRESSION = compile(builder.toString(), CASE_INSENSITIVE);
130145
}
131146

132147
/**
@@ -430,6 +445,20 @@ public static List<javax.persistence.criteria.Order> toOrders(Sort sort, Root<?>
430445
return orders;
431446
}
432447

448+
/**
449+
* Returns whether the given JPQL query contains a constructor expression.
450+
*
451+
* @param query must not be {@literal null} or empty.
452+
* @return
453+
* @since 1.10
454+
*/
455+
public static boolean hasConstructorExpression(String query) {
456+
457+
Assert.hasText(query, "Query must not be null or empty!");
458+
459+
return CONSTRUCTOR_EXPRESSION.matcher(query).find();
460+
}
461+
433462
/**
434463
* Creates a criteria API {@link javax.persistence.criteria.Order} from the given {@link Order}.
435464
*

src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2015 the original author or authors.
2+
* Copyright 2013-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -45,6 +45,7 @@ class StringQuery {
4545
private final String query;
4646
private final List<ParameterBinding> bindings;
4747
private final String alias;
48+
private final boolean hasConstructorExpression;
4849

4950
/**
5051
* Creates a new {@link StringQuery} from the given JPQL query.
@@ -59,6 +60,7 @@ public StringQuery(String query) {
5960
this.query = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query,
6061
this.bindings);
6162
this.alias = QueryUtils.detectAlias(query);
63+
this.hasConstructorExpression = QueryUtils.hasConstructorExpression(query);
6264
}
6365

6466
/**
@@ -133,6 +135,16 @@ public ParameterBinding getBindingFor(int position) {
133135
throw new IllegalArgumentException(String.format("No parameter binding found for position %s!", position));
134136
}
135137

138+
/**
139+
* Returns whether the query is using a constructor expression.
140+
*
141+
* @return
142+
* @since 1.10
143+
*/
144+
public boolean hasConstructorExpression() {
145+
return hasConstructorExpression;
146+
}
147+
136148
/**
137149
* A parser that extracts the parameter bindings from a given query string.
138150
*
@@ -328,8 +340,8 @@ public String getKeyword() {
328340
}
329341

330342
/**
331-
* Return the appropriate {@link ParameterBindingType} for the given {@link String}. Returns {@keyword
332-
* #AS_IS} in case no other {@link ParameterBindingType} could be found.
343+
* Return the appropriate {@link ParameterBindingType} for the given {@link String}. Returns {@keyword #AS_IS} in
344+
* case no other {@link ParameterBindingType} could be found.
333345
*
334346
* @param typeSource
335347
* @return

src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2015 the original author or authors.
2+
* Copyright 2013-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -312,16 +312,16 @@ public void detectsInBindingWithSpecialCharactersAndWordCharactersMixedInParenth
312312
public void rejectsDifferentBindingsForRepeatedParameter2() {
313313
new StringQuery("select u from User u where u.firstname like ?1 and u.lastname like %?1");
314314
}
315-
315+
316316
/**
317317
* @see @DATAJPA-712
318318
*/
319319
@Test
320320
public void shouldReplaceAllNamedExpressionParametersWithInClause() {
321-
321+
322322
StringQuery query = new StringQuery("select a from A a where a.b in :#{#bs} and a.c in :#{#cs}");
323323
String queryString = query.getQueryString();
324-
324+
325325
assertThat(queryString, is("select a from A a where a.b in :__$synthetic$__1 and a.c in :__$synthetic$__2"));
326326
}
327327

@@ -330,13 +330,24 @@ public void shouldReplaceAllNamedExpressionParametersWithInClause() {
330330
*/
331331
@Test
332332
public void shouldReplaceAllPositionExpressionParametersWithInClause() {
333-
333+
334334
StringQuery query = new StringQuery("select a from A a where a.b in ?#{#bs} and a.c in ?#{#cs}");
335335
String queryString = query.getQueryString();
336-
336+
337337
assertThat(queryString, is("select a from A a where a.b in ?1 and a.c in ?2"));
338338
}
339-
339+
340+
/**
341+
* @see DATAJPA-864
342+
*/
343+
@Test
344+
public void detectsConstructorExpressions() {
345+
346+
assertThat(new StringQuery("select new Dto(a.foo, a.bar) from A a").hasConstructorExpression(), is(true));
347+
assertThat(new StringQuery("select new Dto (a.foo, a.bar) from A a").hasConstructorExpression(), is(true));
348+
assertThat(new StringQuery("select a from A a").hasConstructorExpression(), is(false));
349+
}
350+
340351
private void assertPositionalBinding(Class<? extends ParameterBinding> bindingType, Integer position,
341352
ParameterBinding expectedBinding) {
342353

0 commit comments

Comments
 (0)