Skip to content

Commit 4e75ead

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. Remove `spring.data.jpa.query.native.parser` option introduced earlier with #2989 Closes #3622 Original pull request: #3527
1 parent 1206385 commit 4e75ead

File tree

49 files changed

+1063
-703
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

+1063
-703
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: 13 additions & 16 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;
@@ -83,46 +84,39 @@
8384
* Returns the postfix to be used when looking up custom repository implementations. Defaults to {@literal Impl}. So
8485
* for a repository named {@code PersonRepository} the corresponding implementation class will be looked up scanning
8586
* for {@code PersonRepositoryImpl}.
86-
*
87-
* @return
8887
*/
8988
String repositoryImplementationPostfix() default "Impl";
9089

9190
/**
9291
* Configures the location of where to find the Spring Data named queries properties file. Will default to
9392
* {@code META-INF/jpa-named-queries.properties}.
94-
*
95-
* @return
9693
*/
9794
String namedQueriesLocation() default "";
9895

9996
/**
10097
* Returns the key of the {@link QueryLookupStrategy} to be used for lookup queries for query methods. Defaults to
10198
* {@link Key#CREATE_IF_NOT_FOUND}.
102-
*
103-
* @return
10499
*/
105100
Key queryLookupStrategy() default Key.CREATE_IF_NOT_FOUND;
106101

107102
/**
108103
* Returns the {@link FactoryBean} class to be used for each repository instance. Defaults to
109104
* {@link JpaRepositoryFactoryBean}.
110-
*
111-
* @return
112105
*/
113106
Class<?> repositoryFactoryBeanClass() default JpaRepositoryFactoryBean.class;
114107

115108
/**
116109
* Configure the repository base class to be used to create repository proxies for this particular configuration.
117110
*
118-
* @return
119111
* @since 1.9
120112
*/
121113
Class<?> repositoryBaseClass() default DefaultRepositoryBaseClass.class;
122114

123115
/**
124116
* Configure a specific {@link BeanNameGenerator} to be used when creating the repository beans.
125-
* @return the {@link BeanNameGenerator} to be used or the base {@link BeanNameGenerator} interface to indicate context default.
117+
*
118+
* @return the {@link BeanNameGenerator} to be used or the base {@link BeanNameGenerator} interface to indicate
119+
* context default.
126120
* @since 3.4
127121
*/
128122
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@@ -132,22 +126,18 @@
132126
/**
133127
* Configures the name of the {@link EntityManagerFactory} bean definition to be used to create repositories
134128
* discovered through this annotation. Defaults to {@code entityManagerFactory}.
135-
*
136-
* @return
137129
*/
138130
String entityManagerFactoryRef() default "entityManagerFactory";
139131

140132
/**
141133
* Configures the name of the {@link PlatformTransactionManager} bean definition to be used to create repositories
142134
* discovered through this annotation. Defaults to {@code transactionManager}.
143-
*
144-
* @return
145135
*/
146136
String transactionManagerRef() default "transactionManager";
147137

148138
/**
149139
* Configures whether nested repository-interfaces (e.g. defined as inner classes) should be discovered by the
150-
* repositories infrastructure.
140+
* repository infrastructure.
151141
*/
152142
boolean considerNestedRepositories() default false;
153143

@@ -169,7 +159,6 @@
169159
* completed its bootstrap. {@link BootstrapMode#DEFERRED} is fundamentally the same as {@link BootstrapMode#LAZY},
170160
* but triggers repository initialization when the application context finishes its bootstrap.
171161
*
172-
* @return
173162
* @since 2.1
174163
*/
175164
BootstrapMode bootstrapMode() default BootstrapMode.DEFAULT;
@@ -181,4 +170,12 @@
181170
* @return a single character used for escaping.
182171
*/
183172
char escapeCharacter() default '\\';
173+
174+
/**
175+
* Configures the {@link QueryEnhancerSelector} to select a query enhancer for query introspection and transformation.
176+
*
177+
* @return a {@link QueryEnhancerSelector} class providing a no-args constructor.
178+
* @since 4.0
179+
*/
180+
Class<? extends QueryEnhancerSelector> queryEnhancerSelector() default QueryEnhancerSelector.DefaultQueryEnhancerSelector.class;
184181
}

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
@@ -122,6 +122,11 @@ public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSo
122122
}
123123
builder.addPropertyValue(ESCAPE_CHARACTER_PROPERTY, getEscapeCharacter(source).orElse('\\'));
124124
builder.addPropertyReference("mappingContext", JPA_MAPPING_CONTEXT_BEAN_NAME);
125+
126+
if (source instanceof AnnotationRepositoryConfigurationSource) {
127+
builder.addPropertyValue("queryEnhancerSelector",
128+
source.getAttribute("queryEnhancerSelector", Class.class).orElse(null));
129+
}
125130
}
126131

127132
@Override

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

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
5656

5757
private final StringQuery query;
5858
private final Map<Class<?>, Boolean> knownProjections = new ConcurrentHashMap<>();
59-
private final Lazy<DeclaredQuery> countQuery;
59+
private final Lazy<IntrospectedQuery> countQuery;
6060
private final ValueExpressionDelegate valueExpressionDelegate;
6161
private final QueryRewriter queryRewriter;
6262
private final QuerySortRewriter querySortRewriter;
@@ -71,37 +71,32 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
7171
* @param em must not be {@literal null}.
7272
* @param queryString must not be {@literal null}.
7373
* @param countQueryString must not be {@literal null}.
74-
* @param queryRewriter must not be {@literal null}.
75-
* @param valueExpressionDelegate must not be {@literal null}.
74+
* @param queryConfiguration must not be {@literal null}.
7675
*/
7776
public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,
78-
@Nullable String countQueryString, QueryRewriter queryRewriter, ValueExpressionDelegate valueExpressionDelegate) {
77+
@Nullable String countQueryString, JpaQueryConfiguration queryConfiguration) {
7978

8079
super(method, em);
8180

8281
Assert.hasText(queryString, "Query string must not be null or empty");
83-
Assert.notNull(valueExpressionDelegate, "ValueExpressionDelegate must not be null");
84-
Assert.notNull(queryRewriter, "QueryRewriter must not be null");
82+
Assert.notNull(queryConfiguration, "JpaQueryConfiguration must not be null");
8583

86-
this.valueExpressionDelegate = valueExpressionDelegate;
84+
this.valueExpressionDelegate = queryConfiguration.getValueExpressionDelegate();
8785
this.valueExpressionContextProvider = valueExpressionDelegate.createValueContextProvider(method.getParameters());
88-
this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), valueExpressionDelegate,
89-
method.isNativeQuery());
86+
this.query = ExpressionBasedStringQuery.create(queryString, method, queryConfiguration);
9087

9188
this.countQuery = Lazy.of(() -> {
9289

9390
if (StringUtils.hasText(countQueryString)) {
94-
95-
return new ExpressionBasedStringQuery(countQueryString, method.getEntityInformation(), valueExpressionDelegate,
96-
method.isNativeQuery());
91+
return ExpressionBasedStringQuery.create(countQueryString, method, queryConfiguration);
9792
}
9893

99-
return query.deriveCountQuery(method.getCountQueryProjection());
94+
return this.query.deriveCountQuery(method.getCountQueryProjection());
10095
});
10196

10297
this.countParameterBinder = Lazy.of(() -> this.createBinder(this.countQuery.get()));
10398

104-
this.queryRewriter = queryRewriter;
99+
this.queryRewriter = queryConfiguration.getQueryRewriter(method);
105100

106101
JpaParameters parameters = method.getParameters();
107102

@@ -115,7 +110,7 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri
115110
}
116111
}
117112

118-
Assert.isTrue(method.isNativeQuery() || !query.usesJdbcStyleParameters(),
113+
Assert.isTrue(method.isNativeQuery() || !this.query.usesJdbcStyleParameters(),
119114
"JDBC style parameters (?) are not supported for JPA queries");
120115
}
121116

@@ -217,7 +212,7 @@ protected ParameterBinder createBinder() {
217212
return createBinder(query);
218213
}
219214

220-
protected ParameterBinder createBinder(DeclaredQuery query) {
215+
protected ParameterBinder createBinder(IntrospectedQuery query) {
221216
return ParameterBinderFactory.createQueryAwareBinder(getQueryMethod().getParameters(), query,
222217
valueExpressionDelegate, valueExpressionContextProvider);
223218
}
@@ -243,14 +238,14 @@ protected Query doCreateCountQuery(JpaParametersParameterAccessor accessor) {
243238
/**
244239
* @return the query
245240
*/
246-
public DeclaredQuery getQuery() {
241+
public EntityQuery getQuery() {
247242
return query;
248243
}
249244

250245
/**
251246
* @return the countQuery
252247
*/
253-
public DeclaredQuery getCountQuery() {
248+
public IntrospectedQuery getCountQuery() {
254249
return countQuery.get();
255250
}
256251

@@ -292,16 +287,15 @@ protected String potentiallyRewriteQuery(String originalQuery, Sort sort, @Nulla
292287
}
293288

294289
String applySorting(CachableQuery cachableQuery) {
295-
296-
return QueryEnhancerFactory.forQuery(cachableQuery.getDeclaredQuery())
290+
return cachableQuery.getDeclaredQuery().getQueryEnhancer()
297291
.rewrite(new DefaultQueryRewriteInformation(cachableQuery.getSort(), cachableQuery.getReturnedType()));
298292
}
299293

300294
/**
301295
* Query Sort Rewriter interface.
302296
*/
303297
interface QuerySortRewriter {
304-
String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedType);
298+
String getSorted(StringQuery query, Sort sort, ReturnedType returnedType);
305299
}
306300

307301
/**
@@ -311,25 +305,24 @@ enum SimpleQuerySortRewriter implements QuerySortRewriter {
311305

312306
INSTANCE;
313307

314-
public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedType) {
315-
316-
return QueryEnhancerFactory.forQuery(query).rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
308+
public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType) {
309+
return query.getQueryEnhancer().rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
317310
}
318311
}
319312

320313
static class UnsortedCachingQuerySortRewriter implements QuerySortRewriter {
321314

322315
private volatile @Nullable String cachedQueryString;
323316

324-
public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedType) {
317+
public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType) {
325318

326319
if (sort.isSorted()) {
327320
throw new UnsupportedOperationException("NoOpQueryCache does not support sorting");
328321
}
329322

330323
String cachedQueryString = this.cachedQueryString;
331324
if (cachedQueryString == null) {
332-
this.cachedQueryString = cachedQueryString = QueryEnhancerFactory.forQuery(query)
325+
this.cachedQueryString = cachedQueryString = query.getQueryEnhancer()
333326
.rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
334327
}
335328

@@ -348,7 +341,7 @@ class CachingQuerySortRewriter implements QuerySortRewriter {
348341
private volatile @Nullable String cachedQueryString;
349342

350343
@Override
351-
public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedType) {
344+
public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType) {
352345

353346
if (sort.isUnsorted()) {
354347

@@ -373,21 +366,21 @@ public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedTyp
373366
*/
374367
static class CachableQuery {
375368

376-
private final DeclaredQuery declaredQuery;
369+
private final StringQuery query;
377370
private final String queryString;
378371
private final Sort sort;
379372
private final ReturnedType returnedType;
380373

381-
CachableQuery(DeclaredQuery query, Sort sort, ReturnedType returnedType) {
374+
CachableQuery(StringQuery query, Sort sort, ReturnedType returnedType) {
382375

383-
this.declaredQuery = query;
376+
this.query = query;
384377
this.queryString = query.getQueryString();
385378
this.sort = sort;
386379
this.returnedType = returnedType;
387380
}
388381

389-
DeclaredQuery getDeclaredQuery() {
390-
return declaredQuery;
382+
StringQuery getDeclaredQuery() {
383+
return query;
391384
}
392385

393386
Sort getSort() {

0 commit comments

Comments
 (0)