Skip to content

Commit 5d41ada

Browse files
mp911dechristophstrobl
authored andcommitted
Polishing.
Simplify Querydsl templates retrieve and String query caching. Update documentation. Skip selection list rewriting if the returned type is an interface. Encapsulate rewrite information for Query. Reformat code. See: #2327 Original Pull Request: #3654
1 parent 755fe26 commit 5d41ada

13 files changed

+197
-87
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java

+38-22
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323
import jakarta.persistence.TupleElement;
2424
import jakarta.persistence.TypedQuery;
2525

26-
import java.lang.reflect.InvocationTargetException;
27-
import java.util.ArrayList;
2826
import java.util.Arrays;
2927
import java.util.Collection;
3028
import java.util.HashMap;
@@ -34,6 +32,7 @@
3432
import java.util.function.UnaryOperator;
3533
import java.util.stream.Collectors;
3634

35+
import org.springframework.beans.BeanUtils;
3736
import org.springframework.core.convert.converter.Converter;
3837
import org.springframework.data.jpa.provider.PersistenceProvider;
3938
import org.springframework.data.jpa.repository.EntityGraph;
@@ -46,14 +45,16 @@
4645
import org.springframework.data.jpa.repository.query.JpaQueryExecution.StreamExecution;
4746
import org.springframework.data.jpa.repository.support.QueryHints;
4847
import org.springframework.data.jpa.util.JpaMetamodel;
48+
import org.springframework.data.mapping.PreferredConstructor;
49+
import org.springframework.data.mapping.model.PreferredConstructorDiscoverer;
4950
import org.springframework.data.repository.query.RepositoryQuery;
5051
import org.springframework.data.repository.query.ResultProcessor;
5152
import org.springframework.data.repository.query.ReturnedType;
5253
import org.springframework.data.util.Lazy;
5354
import org.springframework.jdbc.support.JdbcUtils;
5455
import org.springframework.lang.Nullable;
5556
import org.springframework.util.Assert;
56-
import org.springframework.util.ClassUtils;
57+
import org.springframework.util.ReflectionUtils;
5758

5859
/**
5960
* Abstract base class to implement {@link RepositoryQuery}s.
@@ -288,8 +289,8 @@ protected Class<?> getTypeToRead(ReturnedType returnedType) {
288289

289290
return returnedType.isProjecting() && returnedType.getReturnedType().isInterface()
290291
&& !getMetamodel().isJpaManaged(returnedType.getReturnedType()) //
291-
? Tuple.class //
292-
: null;
292+
? Tuple.class //
293+
: null;
293294
}
294295

295296
/**
@@ -314,6 +315,10 @@ public static class TupleConverter implements Converter<Object, Object> {
314315

315316
private final UnaryOperator<Tuple> tupleWrapper;
316317

318+
private final boolean dtoProjection;
319+
320+
private final @Nullable PreferredConstructor<?, ?> preferredConstructor;
321+
317322
/**
318323
* Creates a new {@link TupleConverter} for the given {@link ReturnedType}.
319324
*
@@ -336,6 +341,14 @@ public TupleConverter(ReturnedType type, boolean nativeQuery) {
336341

337342
this.type = type;
338343
this.tupleWrapper = nativeQuery ? FallbackTupleWrapper::new : UnaryOperator.identity();
344+
this.dtoProjection = type.isProjecting() && !type.getReturnedType().isInterface()
345+
&& !type.getInputProperties().isEmpty();
346+
347+
if (this.dtoProjection) {
348+
this.preferredConstructor = PreferredConstructorDiscoverer.discover(String.class);
349+
} else {
350+
this.preferredConstructor = null;
351+
}
339352
}
340353

341354
@Override
@@ -356,23 +369,26 @@ public Object convert(Object source) {
356369
}
357370
}
358371

359-
if(type.isProjecting() && !type.getReturnedType().isInterface() && !type.getInputProperties().isEmpty()) {
360-
List<Object> ctorArgs = new ArrayList<>(type.getInputProperties().size());
361-
type.getInputProperties().forEach(it -> {
362-
ctorArgs.add(tuple.get(it));
363-
});
364-
try {
365-
return type.getReturnedType().getConstructor(ctorArgs.stream().map(Object::getClass).toArray(Class<?>[]::new)).newInstance(ctorArgs.toArray());
366-
} catch (InstantiationException e) {
367-
throw new RuntimeException(e);
368-
} catch (IllegalAccessException e) {
369-
throw new RuntimeException(e);
370-
} catch (InvocationTargetException e) {
371-
throw new RuntimeException(e);
372-
} catch (NoSuchMethodException e) {
373-
throw new RuntimeException(e);
374-
}
375-
}
372+
if (dtoProjection) {
373+
374+
Object[] ctorArgs = new Object[type.getInputProperties().size()];
375+
376+
for (int i = 0; i < type.getInputProperties().size(); i++) {
377+
ctorArgs[i] = tuple.get(i);
378+
}
379+
380+
try {
381+
382+
if (preferredConstructor.getParameterCount() == ctorArgs.length) {
383+
return BeanUtils.instantiateClass(preferredConstructor.getConstructor(), ctorArgs);
384+
}
385+
386+
return BeanUtils.instantiateClass(type.getReturnedType()
387+
.getConstructor(Arrays.stream(ctorArgs).map(Object::getClass).toArray(Class<?>[]::new)), ctorArgs);
388+
} catch (ReflectiveOperationException e) {
389+
ReflectionUtils.handleReflectionException(e);
390+
}
391+
}
376392

377393
return new TupleBackedMap(tupleWrapper.apply(tuple));
378394
}

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

+20-32
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
6969
* @param valueExpressionDelegate must not be {@literal null}.
7070
*/
7171
public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,
72-
@Nullable String countQueryString, QueryRewriter queryRewriter,
73-
ValueExpressionDelegate valueExpressionDelegate) {
72+
@Nullable String countQueryString, QueryRewriter queryRewriter, ValueExpressionDelegate valueExpressionDelegate) {
7473

7574
super(method, em);
7675

@@ -99,15 +98,17 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri
9998
});
10099

101100
this.queryRewriter = queryRewriter;
102-
ReturnedType returnedType = method.getResultProcessor().getReturnedType();
103101

104102
JpaParameters parameters = method.getParameters();
105-
if ((parameters.hasPageableParameter() || parameters.hasSortParameter()) && !parameters.hasDynamicProjection()) {
106-
this.querySortRewriter = new CachingQuerySortRewriter();
107-
} else if (returnedType.isProjecting() && !returnedType.getReturnedType().isInterface()) {
108-
this.querySortRewriter = new ProjectingSortRewriter();
103+
104+
if (parameters.hasDynamicProjection()) {
105+
this.querySortRewriter = SimpleQuerySortRewriter.INSTANCE;
109106
} else {
110-
this.querySortRewriter = NoOpQuerySortRewriter.INSTANCE;
107+
if (parameters.hasPageableParameter() || parameters.hasSortParameter()) {
108+
this.querySortRewriter = new CachingQuerySortRewriter();
109+
} else {
110+
this.querySortRewriter = new UnsortedCachingQuerySortRewriter();
111+
}
111112
}
112113

113114
Assert.isTrue(method.isNativeQuery() || !query.usesJdbcStyleParameters(),
@@ -119,19 +120,13 @@ public Query doCreateQuery(JpaParametersParameterAccessor accessor) {
119120

120121
Sort sort = accessor.getSort();
121122
ResultProcessor processor = getQueryMethod().getResultProcessor().withDynamicProjection(accessor);
122-
123-
String sortedQueryString = null;
124-
if(querySortRewriter.equals(NoOpQuerySortRewriter.INSTANCE) && accessor.findDynamicProjection() != null && !accessor.findDynamicProjection().isInterface()) {
125-
sortedQueryString = getSortedQueryString(new ProjectingSortRewriter(), query, sort, processor.getReturnedType());
126-
} else {
127-
sortedQueryString = getSortedQueryString(sort, processor.getReturnedType());
128-
}
129-
130-
Query query = createJpaQuery(sortedQueryString, sort, accessor.getPageable(), processor.getReturnedType());
123+
ReturnedType returnedType = processor.getReturnedType();
124+
String sortedQueryString = getSortedQueryString(sort, returnedType);
125+
Query query = createJpaQuery(sortedQueryString, sort, accessor.getPageable(), returnedType);
131126

132127
QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata(sortedQueryString, query);
133128

134-
// it is ok to reuse the binding contained in the ParameterBinder although we create a new query String because the
129+
// it is ok to reuse the binding contained in the ParameterBinder, although we create a new query String because the
135130
// parameters in the query do not change.
136131
return parameterBinder.get().bindAndPrepare(query, metadata, accessor);
137132
}
@@ -140,10 +135,6 @@ String getSortedQueryString(Sort sort, ReturnedType returnedType) {
140135
return querySortRewriter.getSorted(query, sort, returnedType);
141136
}
142137

143-
private static String getSortedQueryString(QuerySortRewriter rewriter, DeclaredQuery query, Sort sort, ReturnedType returnedType) {
144-
return rewriter.getSorted(query, sort, returnedType);
145-
}
146-
147138
@Override
148139
protected ParameterBinder createBinder() {
149140
return createBinder(query);
@@ -223,8 +214,8 @@ protected String potentiallyRewriteQuery(String originalQuery, Sort sort, @Nulla
223214

224215
String applySorting(CachableQuery cachableQuery) {
225216

226-
return QueryEnhancerFactory.forQuery(cachableQuery.getDeclaredQuery()).rewrite(cachableQuery.getSort(),
227-
cachableQuery.getReturnedType());
217+
return QueryEnhancerFactory.forQuery(cachableQuery.getDeclaredQuery())
218+
.rewrite(new DefaultQueryRewriteInformation(cachableQuery.getSort(), cachableQuery.getReturnedType()));
228219
}
229220

230221
/**
@@ -237,21 +228,17 @@ interface QuerySortRewriter {
237228
/**
238229
* No-op query rewriter.
239230
*/
240-
enum NoOpQuerySortRewriter implements QuerySortRewriter {
231+
enum SimpleQuerySortRewriter implements QuerySortRewriter {
241232

242233
INSTANCE;
243234

244235
public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedType) {
245236

246-
if (sort.isSorted()) {
247-
throw new UnsupportedOperationException("NoOpQueryCache does not support sorting");
248-
}
249-
250-
return query.getQueryString();
237+
return QueryEnhancerFactory.forQuery(query).rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
251238
}
252239
}
253240

254-
static class ProjectingSortRewriter implements QuerySortRewriter {
241+
static class UnsortedCachingQuerySortRewriter implements QuerySortRewriter {
255242

256243
private volatile String cachedQueryString;
257244

@@ -263,7 +250,8 @@ public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedTyp
263250

264251
String cachedQueryString = this.cachedQueryString;
265252
if (cachedQueryString == null) {
266-
this.cachedQueryString = cachedQueryString = QueryEnhancerFactory.forQuery(query).rewrite(sort, returnedType);
253+
this.cachedQueryString = cachedQueryString = QueryEnhancerFactory.forQuery(query)
254+
.rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
267255
}
268256

269257
return cachedQueryString;

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import java.util.Set;
1919

2020
import org.springframework.data.domain.Sort;
21-
import org.springframework.data.repository.query.ReturnedType;
2221
import org.springframework.lang.Nullable;
2322

2423
/**
@@ -54,8 +53,8 @@ public String applySorting(Sort sort, @Nullable String alias) {
5453
}
5554

5655
@Override
57-
public String rewrite(Sort sort, ReturnedType returnedType) {
58-
return QueryUtils.applySorting(this.query.getQueryString(), sort, alias);
56+
public String rewrite(QueryRewriteInformation rewriteInformation) {
57+
return QueryUtils.applySorting(this.query.getQueryString(), rewriteInformation.getSort(), alias);
5958
}
6059

6160
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jpa.repository.query;
17+
18+
import org.springframework.data.domain.Sort;
19+
import org.springframework.data.repository.query.ReturnedType;
20+
21+
/**
22+
* Default {@link org.springframework.data.jpa.repository.query.QueryEnhancer.QueryRewriteInformation} implementation.
23+
*
24+
* @author Mark Paluch
25+
*/
26+
record DefaultQueryRewriteInformation(Sort sort,
27+
ReturnedType returnedType) implements QueryEnhancer.QueryRewriteInformation {
28+
@Override
29+
public Sort getSort() {
30+
return sort();
31+
}
32+
33+
@Override
34+
public ReturnedType getReturnedType() {
35+
return returnedType();
36+
}
37+
}

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DtoProjectionTransformerDelegate.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
* </ul>
3030
*
3131
* @author Mark Paluch
32+
* @since 3.5
3233
*/
3334
class DtoProjectionTransformerDelegate {
3435

@@ -40,7 +41,8 @@ public DtoProjectionTransformerDelegate(ReturnedType returnedType) {
4041

4142
public QueryTokenStream transformSelectionList(QueryTokenStream selectionList) {
4243

43-
if (!returnedType.isProjecting() || selectionList.stream().anyMatch(it -> it.equals(TOKEN_NEW))) {
44+
if (!returnedType.isProjecting() || returnedType.getReturnedType().isInterface()
45+
|| selectionList.stream().anyMatch(it -> it.equals(TOKEN_NEW))) {
4446
return selectionList;
4547
}
4648

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
import java.util.StringJoiner;
5151

5252
import org.springframework.data.domain.Sort;
53-
import org.springframework.data.repository.query.ReturnedType;
5453
import org.springframework.lang.Nullable;
5554
import org.springframework.util.Assert;
5655
import org.springframework.util.CollectionUtils;
@@ -298,17 +297,20 @@ public DeclaredQuery getQuery() {
298297

299298
@Override
300299
public String applySorting(Sort sort) {
301-
return applySorting(sort, detectAlias());
300+
return doApplySorting(sort, detectAlias());
302301
}
303302

304303
@Override
305-
public String rewrite(Sort sort, ReturnedType returnedType) {
306-
return applySorting(sort, primaryAlias);
304+
public String rewrite(QueryRewriteInformation rewriteInformation) {
305+
return doApplySorting(rewriteInformation.getSort(), primaryAlias);
307306
}
308307

309308
@Override
310309
public String applySorting(Sort sort, @Nullable String alias) {
310+
return doApplySorting(sort, alias);
311+
}
311312

313+
private String doApplySorting(Sort sort, @Nullable String alias) {
312314
String queryString = query.getQueryString();
313315
Assert.hasText(queryString, "Query must not be null or empty");
314316

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryEnhancer.java

+10-2
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,9 @@ public String applySorting(Sort sort) {
200200
}
201201

202202
@Override
203-
public String rewrite(Sort sort, ReturnedType returnedType) {
204-
return QueryRenderer.TokenRenderer.render(sortFunction.apply(sort, detectAlias(), returnedType).visit(context));
203+
public String rewrite(QueryRewriteInformation rewriteInformation) {
204+
return QueryRenderer.TokenRenderer.render(sortFunction
205+
.apply(rewriteInformation.getSort(), detectAlias(), rewriteInformation.getReturnedType()).visit(context));
205206
}
206207

207208
/**
@@ -319,6 +320,13 @@ public static JpqlQueryParser parseQuery(String query) throws BadJpqlGrammarExce
319320
}
320321
}
321322

323+
/**
324+
* Functional interface to rewrite a query considering {@link Sort} and {@link ReturnedType}. The function returns a
325+
* visitor object that can visit the parsed query tree.
326+
*
327+
* @since 3.5
328+
*/
329+
@FunctionalInterface
322330
interface SortedQueryRewriteFunction {
323331

324332
ParseTreeVisitor<? extends Object> apply(Sort sort, String primaryAlias, @Nullable ReturnedType returnedType);

0 commit comments

Comments
 (0)