Skip to content

Commit 70c8a65

Browse files
committed
Hacking.
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 d1675a5 commit 70c8a65

Some content is hidden

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

43 files changed

+938
-468
lines changed

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
@@ -27,6 +27,7 @@
2727
import org.springframework.context.annotation.ComponentScan.Filter;
2828
import org.springframework.context.annotation.Import;
2929
import org.springframework.context.annotation.Lazy;
30+
import org.springframework.data.jpa.repository.query.QueryEnhancerSelector;
3031
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
3132
import org.springframework.data.repository.config.BootstrapMode;
3233
import org.springframework.data.repository.config.DefaultRepositoryBaseClass;
@@ -173,4 +174,11 @@
173174
* @return a single character used for escaping.
174175
*/
175176
char escapeCharacter() default '\\';
177+
178+
/**
179+
* Configures the {@link QueryEnhancerSelector} to select a query enhancer for query introspection and transformation.
180+
*
181+
* @return a {@link QueryEnhancerSelector} class providing a no-args constructor.
182+
*/
183+
Class<? extends QueryEnhancerSelector> queryEnhancerSelector() default QueryEnhancerSelector.DefaultQueryEnhancerSelector.class;
176184
}

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: 21 additions & 32 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 IntrospectedQuery query;
52+
private final Lazy<IntrospectedQuery> countQuery;
5353
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
5454
private final SpelExpressionParser parser;
5555
private final QueryParameterSetter.QueryMetadataCache metadataCache = new QueryParameterSetter.QueryMetadataCache();
@@ -65,31 +65,20 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
6565
* @param em must not be {@literal null}.
6666
* @param queryString must not be {@literal null}.
6767
* @param countQueryString must not be {@literal null}.
68-
* @param evaluationContextProvider must not be {@literal null}.
69-
* @param parser must not be {@literal null}.
70-
* @param queryRewriter must not be {@literal null}.
68+
* @param queryConfiguration must not be {@literal null}.
7169
*/
7270
public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,
73-
@Nullable String countQueryString, QueryRewriter queryRewriter,
74-
QueryMethodEvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) {
71+
@Nullable String countQueryString, JpaQueryConfiguration queryConfiguration) {
7572

7673
super(method, em);
7774

78-
Assert.hasText(queryString, "Query string must not be null or empty");
79-
Assert.notNull(evaluationContextProvider, "ExpressionEvaluationContextProvider must not be null");
80-
Assert.notNull(parser, "Parser must not be null");
81-
Assert.notNull(queryRewriter, "QueryRewriter must not be null");
82-
83-
this.evaluationContextProvider = evaluationContextProvider;
84-
this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), parser,
85-
method.isNativeQuery());
75+
this.evaluationContextProvider = queryConfiguration.getEvaluationContextProvider();
76+
this.query = ExpressionBasedStringQuery.create(queryString, method, queryConfiguration);
8677

8778
this.countQuery = Lazy.of(() -> {
8879

8980
if (StringUtils.hasText(countQueryString)) {
90-
91-
return new ExpressionBasedStringQuery(countQueryString, method.getEntityInformation(), parser,
92-
method.isNativeQuery());
81+
return ExpressionBasedStringQuery.create(countQueryString, method, queryConfiguration);
9382
}
9483

9584
return query.deriveCountQuery(method.getCountQueryProjection());
@@ -99,8 +88,8 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri
9988
return this.createBinder(this.countQuery.get());
10089
});
10190

102-
this.parser = parser;
103-
this.queryRewriter = queryRewriter;
91+
this.parser = queryConfiguration.getParser();
92+
this.queryRewriter = queryConfiguration.getQueryRewriter(method);
10493

10594
JpaParameters parameters = method.getParameters();
10695
if (parameters.hasPageableParameter() || parameters.hasSortParameter()) {
@@ -135,7 +124,7 @@ protected ParameterBinder createBinder() {
135124
return createBinder(query);
136125
}
137126

138-
protected ParameterBinder createBinder(DeclaredQuery query) {
127+
protected ParameterBinder createBinder(IntrospectedQuery query) {
139128
return ParameterBinderFactory.createQueryAwareBinder(getQueryMethod().getParameters(), query, parser,
140129
evaluationContextProvider);
141130
}
@@ -160,14 +149,14 @@ protected Query doCreateCountQuery(JpaParametersParameterAccessor accessor) {
160149
/**
161150
* @return the query
162151
*/
163-
public DeclaredQuery getQuery() {
152+
public IntrospectedQuery getQuery() {
164153
return query;
165154
}
166155

167156
/**
168157
* @return the countQuery
169158
*/
170-
public DeclaredQuery getCountQuery() {
159+
public IntrospectedQuery getCountQuery() {
171160
return countQuery.get();
172161
}
173162

@@ -217,7 +206,7 @@ String applySorting(CachableQuery cachableQuery) {
217206
* Query Sort Rewriter interface.
218207
*/
219208
interface QuerySortRewriter {
220-
String getSorted(DeclaredQuery query, Sort sort);
209+
String getSorted(IntrospectedQuery query, Sort sort);
221210
}
222211

223212
/**
@@ -226,7 +215,7 @@ interface QuerySortRewriter {
226215
enum NoOpQuerySortRewriter implements QuerySortRewriter {
227216
INSTANCE;
228217

229-
public String getSorted(DeclaredQuery query, Sort sort) {
218+
public String getSorted(IntrospectedQuery query, Sort sort) {
230219

231220
if (sort.isSorted()) {
232221
throw new UnsupportedOperationException("NoOpQueryCache does not support sorting");
@@ -245,7 +234,7 @@ class CachingQuerySortRewriter implements QuerySortRewriter {
245234
AbstractStringBasedJpaQuery.this::applySorting);
246235

247236
@Override
248-
public String getSorted(DeclaredQuery query, Sort sort) {
237+
public String getSorted(IntrospectedQuery query, Sort sort) {
249238

250239
if (sort.isUnsorted()) {
251240
return query.getQueryString();
@@ -264,19 +253,19 @@ public String getSorted(DeclaredQuery query, Sort sort) {
264253
*/
265254
static class CachableQuery {
266255

267-
private final DeclaredQuery declaredQuery;
256+
private final IntrospectedQuery introspectedQuery;
268257
private final String queryString;
269258
private final Sort sort;
270259

271-
CachableQuery(DeclaredQuery query, Sort sort) {
260+
CachableQuery(IntrospectedQuery query, Sort sort) {
272261

273-
this.declaredQuery = query;
262+
this.introspectedQuery = query;
274263
this.queryString = query.getQueryString();
275264
this.sort = sort;
276265
}
277266

278-
DeclaredQuery getDeclaredQuery() {
279-
return declaredQuery;
267+
IntrospectedQuery getDeclaredQuery() {
268+
return introspectedQuery;
280269
}
281270

282271
Sort getSort() {
@@ -285,7 +274,7 @@ Sort getSort() {
285274

286275
@Nullable
287276
String getAlias() {
288-
return declaredQuery.getAlias();
277+
return introspectedQuery.getAlias();
289278
}
290279

291280
@Override
Lines changed: 17 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018-2024 the original author or authors.
2+
* Copyright 2024 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.
@@ -15,99 +15,42 @@
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
*
26-
* @author Jens Schauder
27-
* @author Diego Krupitza
28-
* @since 2.0.3
21+
* @author Mark Paluch
2922
*/
30-
interface DeclaredQuery {
23+
public interface DeclaredQuery {
3124

3225
/**
33-
* Creates a {@literal DeclaredQuery} from a query {@literal String}.
26+
* Creates a DeclaredQuery for a JPQL query.
3427
*
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.
28+
* @param query the JPQL query string.
29+
* @return
3830
*/
39-
static DeclaredQuery of(@Nullable String query, boolean nativeQuery) {
40-
return ObjectUtils.isEmpty(query) ? EmptyDeclaredQuery.EMPTY_QUERY : new StringQuery(query, nativeQuery);
31+
static DeclaredQuery jpql(String query) {
32+
return new DefaultDeclaredQuery(query, false);
4133
}
4234

4335
/**
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.
36+
* Creates a DeclaredQuery for a native query.
6337
*
64-
* @since 1.10
38+
* @param query the native query string.
39+
* @return
6540
*/
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;
41+
static DeclaredQuery nativeQuery(String query) {
42+
return new DefaultDeclaredQuery(query, true);
9443
}
9544

9645
/**
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
46+
* Returns the query string.
10247
*/
103-
boolean usesJdbcStyleParameters();
48+
String getQueryString();
10449

10550
/**
10651
* Return whether the query is a native query of not.
10752
*
10853
* @return <code>true</code> if native query otherwise <code>false</code>
10954
*/
110-
default boolean isNativeQuery() {
111-
return false;
112-
}
55+
boolean isNativeQuery();
11356
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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.util.ObjectUtils;
19+
20+
/**
21+
* @author Mark Paluch
22+
*/
23+
class DefaultDeclaredQuery implements DeclaredQuery {
24+
25+
private final String query;
26+
private final boolean nativeQuery;
27+
28+
DefaultDeclaredQuery(String query, boolean nativeQuery) {
29+
this.query = query;
30+
this.nativeQuery = nativeQuery;
31+
}
32+
33+
public String getQueryString() {
34+
return query;
35+
}
36+
37+
public boolean isNativeQuery() {
38+
return nativeQuery;
39+
}
40+
41+
@Override
42+
public boolean equals(Object object) {
43+
if (this == object) {
44+
return true;
45+
}
46+
if (!(object instanceof DefaultDeclaredQuery that)) {
47+
return false;
48+
}
49+
if (nativeQuery != that.nativeQuery) {
50+
return false;
51+
}
52+
return ObjectUtils.nullSafeEquals(query, that.query);
53+
}
54+
55+
@Override
56+
public int hashCode() {
57+
int result = ObjectUtils.nullSafeHashCode(query);
58+
result = 31 * result + (nativeQuery ? 1 : 0);
59+
return result;
60+
}
61+
62+
@Override
63+
public String toString() {
64+
return (isNativeQuery() ? "[native] " : "[JPQL] ") + getQueryString();
65+
}
66+
}

0 commit comments

Comments
 (0)