Skip to content

Commit 886bfaa

Browse files
committed
Introduce QueryEnhancerSelector to configure which QueryEnhancerFactory to use.
Introduce QueryEnhancerSelector to EnableJpaRepositories. Also, split DeclaredQuery into two interfaces to resolve the inner cycle of query introspection while just a value object is being created. Introduce JpaQueryConfiguration to capture a multitude of configuration elements.
1 parent 06dd7e8 commit 886bfaa

File tree

49 files changed

+1064
-694
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1064
-694
lines changed

spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/HqlParserBenchmarks.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ OR TREAT(p AS SmallProject).name LIKE 'Persist%'
5555
OR p.description LIKE "cost overrun"
5656
""";
5757

58-
query = DeclaredQuery.of(s, false);
59-
enhancer = QueryEnhancerFactory.forQuery(query);
58+
query = DeclaredQuery.ofJpql(s);
59+
enhancer = QueryEnhancerFactory.forQuery(query).create(query);
6060
}
6161
}
6262

spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerBenchmarks.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public void doSetup() throws IOException {
5656
select SOME_COLUMN from SOME_OTHER_TABLE where REPORTING_DATE = :REPORTING_DATE
5757
union select SOME_COLUMN from SOME_OTHER_OTHER_TABLE""";
5858

59-
enhancer = new JSqlParserQueryEnhancer(DeclaredQuery.of(s, true));
59+
enhancer = new JSqlParserQueryEnhancer(DeclaredQuery.ofNative(s));
6060
}
6161
}
6262

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/EnableJpaRepositories.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.context.annotation.ComponentScan.Filter;
2929
import org.springframework.context.annotation.Import;
3030
import org.springframework.context.annotation.Lazy;
31+
import org.springframework.data.jpa.repository.query.QueryEnhancerSelector;
3132
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
3233
import org.springframework.data.repository.config.BootstrapMode;
3334
import org.springframework.data.repository.config.DefaultRepositoryBaseClass;
@@ -181,4 +182,11 @@
181182
* @return a single character used for escaping.
182183
*/
183184
char escapeCharacter() default '\\';
185+
186+
/**
187+
* Configures the {@link QueryEnhancerSelector} to select a query enhancer for query introspection and transformation.
188+
*
189+
* @return a {@link QueryEnhancerSelector} class providing a no-args constructor.
190+
*/
191+
Class<? extends QueryEnhancerSelector> queryEnhancerSelector() default QueryEnhancerSelector.DefaultQueryEnhancerSelector.class;
184192
}

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSo
119119
builder.addPropertyReference("entityManager", entityManagerRefs.get(source));
120120
builder.addPropertyValue(ESCAPE_CHARACTER_PROPERTY, getEscapeCharacter(source).orElse('\\'));
121121
builder.addPropertyReference("mappingContext", JPA_MAPPING_CONTEXT_BEAN_NAME);
122+
123+
if (source instanceof AnnotationRepositoryConfigurationSource) {
124+
builder.addPropertyValue("queryEnhancerSelector",
125+
source.getAttribute("queryEnhancerSelector", Class.class).orElse(null));
126+
}
122127
}
123128

124129
@Override

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

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@
4848
*/
4949
abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
5050

51-
private final DeclaredQuery query;
52-
private final Lazy<DeclaredQuery> countQuery;
51+
private final StringQuery query;
52+
private final Lazy<IntrospectedQuery> countQuery;
5353
private final ValueExpressionDelegate valueExpressionDelegate;
5454
private final QueryRewriter queryRewriter;
5555
private final QuerySortRewriter querySortRewriter;
@@ -64,37 +64,32 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
6464
* @param em must not be {@literal null}.
6565
* @param queryString must not be {@literal null}.
6666
* @param countQueryString must not be {@literal null}.
67-
* @param queryRewriter must not be {@literal null}.
68-
* @param valueExpressionDelegate must not be {@literal null}.
67+
* @param queryConfiguration must not be {@literal null}.
6968
*/
7069
public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,
71-
@Nullable String countQueryString, QueryRewriter queryRewriter, ValueExpressionDelegate valueExpressionDelegate) {
70+
@Nullable String countQueryString, JpaQueryConfiguration queryConfiguration) {
7271

7372
super(method, em);
7473

7574
Assert.hasText(queryString, "Query string must not be null or empty");
76-
Assert.notNull(valueExpressionDelegate, "ValueExpressionDelegate must not be null");
77-
Assert.notNull(queryRewriter, "QueryRewriter must not be null");
75+
Assert.notNull(queryConfiguration, "JpaQueryConfiguration must not be null");
7876

79-
this.valueExpressionDelegate = valueExpressionDelegate;
77+
this.valueExpressionDelegate = queryConfiguration.getValueExpressionDelegate();
8078
this.valueExpressionContextProvider = valueExpressionDelegate.createValueContextProvider(method.getParameters());
81-
this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), valueExpressionDelegate,
82-
method.isNativeQuery());
79+
this.query = ExpressionBasedStringQuery.create(queryString, method, queryConfiguration);
8380

8481
this.countQuery = Lazy.of(() -> {
8582

8683
if (StringUtils.hasText(countQueryString)) {
87-
88-
return new ExpressionBasedStringQuery(countQueryString, method.getEntityInformation(), valueExpressionDelegate,
89-
method.isNativeQuery());
84+
return ExpressionBasedStringQuery.create(countQueryString, method, queryConfiguration);
9085
}
9186

92-
return query.deriveCountQuery(method.getCountQueryProjection());
87+
return this.query.deriveCountQuery(method.getCountQueryProjection());
9388
});
9489

9590
this.countParameterBinder = Lazy.of(() -> this.createBinder(this.countQuery.get()));
9691

97-
this.queryRewriter = queryRewriter;
92+
this.queryRewriter = queryConfiguration.getQueryRewriter(method);
9893

9994
JpaParameters parameters = method.getParameters();
10095

@@ -108,7 +103,7 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri
108103
}
109104
}
110105

111-
Assert.isTrue(method.isNativeQuery() || !query.usesJdbcStyleParameters(),
106+
Assert.isTrue(method.isNativeQuery() || !this.query.usesJdbcStyleParameters(),
112107
"JDBC style parameters (?) are not supported for JPA queries");
113108
}
114109

@@ -135,7 +130,7 @@ protected ParameterBinder createBinder() {
135130
return createBinder(query);
136131
}
137132

138-
protected ParameterBinder createBinder(DeclaredQuery query) {
133+
protected ParameterBinder createBinder(IntrospectedQuery query) {
139134
return ParameterBinderFactory.createQueryAwareBinder(getQueryMethod().getParameters(), query,
140135
valueExpressionDelegate, valueExpressionContextProvider);
141136
}
@@ -159,14 +154,14 @@ protected Query doCreateCountQuery(JpaParametersParameterAccessor accessor) {
159154
/**
160155
* @return the query
161156
*/
162-
public DeclaredQuery getQuery() {
157+
public EntityQuery getQuery() {
163158
return query;
164159
}
165160

166161
/**
167162
* @return the countQuery
168163
*/
169-
public DeclaredQuery getCountQuery() {
164+
public IntrospectedQuery getCountQuery() {
170165
return countQuery.get();
171166
}
172167

@@ -207,16 +202,15 @@ protected String potentiallyRewriteQuery(String originalQuery, Sort sort, @Nulla
207202
}
208203

209204
String applySorting(CachableQuery cachableQuery) {
210-
211-
return QueryEnhancerFactory.forQuery(cachableQuery.getDeclaredQuery())
205+
return cachableQuery.getDeclaredQuery().getQueryEnhancer()
212206
.rewrite(new DefaultQueryRewriteInformation(cachableQuery.getSort(), cachableQuery.getReturnedType()));
213207
}
214208

215209
/**
216210
* Query Sort Rewriter interface.
217211
*/
218212
interface QuerySortRewriter {
219-
String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedType);
213+
String getSorted(StringQuery query, Sort sort, ReturnedType returnedType);
220214
}
221215

222216
/**
@@ -226,25 +220,24 @@ enum SimpleQuerySortRewriter implements QuerySortRewriter {
226220

227221
INSTANCE;
228222

229-
public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedType) {
230-
231-
return QueryEnhancerFactory.forQuery(query).rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
223+
public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType) {
224+
return query.getQueryEnhancer().rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
232225
}
233226
}
234227

235228
static class UnsortedCachingQuerySortRewriter implements QuerySortRewriter {
236229

237230
private volatile String cachedQueryString;
238231

239-
public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedType) {
232+
public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType) {
240233

241234
if (sort.isSorted()) {
242235
throw new UnsupportedOperationException("NoOpQueryCache does not support sorting");
243236
}
244237

245238
String cachedQueryString = this.cachedQueryString;
246239
if (cachedQueryString == null) {
247-
this.cachedQueryString = cachedQueryString = QueryEnhancerFactory.forQuery(query)
240+
this.cachedQueryString = cachedQueryString = query.getQueryEnhancer()
248241
.rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
249242
}
250243

@@ -263,7 +256,7 @@ class CachingQuerySortRewriter implements QuerySortRewriter {
263256
private volatile String cachedQueryString;
264257

265258
@Override
266-
public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedType) {
259+
public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType) {
267260

268261
if (sort.isUnsorted()) {
269262

@@ -288,21 +281,21 @@ public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedTyp
288281
*/
289282
static class CachableQuery {
290283

291-
private final DeclaredQuery declaredQuery;
284+
private final StringQuery query;
292285
private final String queryString;
293286
private final Sort sort;
294287
private final ReturnedType returnedType;
295288

296-
CachableQuery(DeclaredQuery query, Sort sort, ReturnedType returnedType) {
289+
CachableQuery(StringQuery query, Sort sort, ReturnedType returnedType) {
297290

298-
this.declaredQuery = query;
291+
this.query = query;
299292
this.queryString = query.getQueryString();
300293
this.sort = sort;
301294
this.returnedType = returnedType;
302295
}
303296

304-
DeclaredQuery getDeclaredQuery() {
305-
return declaredQuery;
297+
StringQuery getDeclaredQuery() {
298+
return query;
306299
}
307300

308301
Sort getSort() {

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

Lines changed: 16 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -15,99 +15,45 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18-
import java.util.List;
19-
20-
import org.springframework.lang.Nullable;
21-
import org.springframework.util.ObjectUtils;
22-
2318
/**
24-
* A wrapper for a String representation of a query offering information about the query.
19+
* Interface defining the contract to represent a declared query.
2520
*
2621
* @author Jens Schauder
2722
* @author Diego Krupitza
23+
* @author Mark Paluch
2824
* @since 2.0.3
2925
*/
30-
interface DeclaredQuery {
26+
public interface DeclaredQuery {
3127

3228
/**
33-
* Creates a {@literal DeclaredQuery} from a query {@literal String}.
29+
* Creates a DeclaredQuery for a JPQL query.
3430
*
35-
* @param query might be {@literal null} or empty.
36-
* @param nativeQuery is a given query is native or not
37-
* @return a {@literal DeclaredQuery} instance even for a {@literal null} or empty argument.
31+
* @param query the JPQL query string.
32+
* @return
3833
*/
39-
static DeclaredQuery of(@Nullable String query, boolean nativeQuery) {
40-
return ObjectUtils.isEmpty(query) ? EmptyDeclaredQuery.EMPTY_QUERY : new StringQuery(query, nativeQuery);
34+
static DeclaredQuery ofJpql(String query) {
35+
return new DefaultDeclaredQuery(query, false);
4136
}
4237

4338
/**
44-
* @return whether the underlying query has at least one named parameter.
45-
*/
46-
boolean hasNamedParameter();
47-
48-
/**
49-
* Returns the query string.
50-
*/
51-
String getQueryString();
52-
53-
/**
54-
* Returns the main alias used in the query.
55-
*
56-
* @return the alias
57-
*/
58-
@Nullable
59-
String getAlias();
60-
61-
/**
62-
* Returns whether the query is using a constructor expression.
39+
* Creates a DeclaredQuery for a native query.
6340
*
64-
* @since 1.10
41+
* @param query the native query string.
42+
* @return
6543
*/
66-
boolean hasConstructorExpression();
67-
68-
/**
69-
* Returns whether the query uses the default projection, i.e. returns the main alias defined for the query.
70-
*/
71-
boolean isDefaultProjection();
72-
73-
/**
74-
* Returns the {@link ParameterBinding}s registered.
75-
*/
76-
List<ParameterBinding> getParameterBindings();
77-
78-
/**
79-
* Creates a new {@literal DeclaredQuery} representing a count query, i.e. a query returning the number of rows to be
80-
* expected from the original query, either derived from the query wrapped by this instance or from the information
81-
* passed as arguments.
82-
*
83-
* @param countQueryProjection an optional return type for the query.
84-
* @return a new {@literal DeclaredQuery} instance.
85-
*/
86-
DeclaredQuery deriveCountQuery(@Nullable String countQueryProjection);
87-
88-
/**
89-
* @return whether paging is implemented in the query itself, e.g. using SpEL expressions.
90-
* @since 2.0.6
91-
*/
92-
default boolean usesPaging() {
93-
return false;
44+
static DeclaredQuery ofNative(String query) {
45+
return new DefaultDeclaredQuery(query, true);
9446
}
9547

9648
/**
97-
* Returns whether the query uses JDBC style parameters, i.e. parameters denoted by a simple ? without any index or
98-
* name.
99-
*
100-
* @return Whether the query uses JDBC style parameters.
101-
* @since 2.0.6
49+
* Returns the query string.
10250
*/
103-
boolean usesJdbcStyleParameters();
51+
String getQueryString();
10452

10553
/**
10654
* Return whether the query is a native query of not.
10755
*
10856
* @return <code>true</code> if native query otherwise <code>false</code>
10957
*/
110-
default boolean isNativeQuery() {
111-
return false;
112-
}
58+
boolean isNativeQuery();
11359
}

0 commit comments

Comments
 (0)