Skip to content

Commit 6c94e0e

Browse files
mp911degregturn
authored andcommitted
Polishing.
Eagerly resolve QueryRewriter instances when creating JPA query objects. Tweak documentation wording. Tweak type names to align with naming scheme. See #2162.
1 parent 264472b commit 6c94e0e

20 files changed

+244
-219
lines changed

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@
8080
String countName() default "";
8181

8282
/**
83-
* Define the {@link QueryRewriter} bean that should be applied to this query after the query is full assembled.
83+
* Define a {@link QueryRewriter} that should be applied to the query string after the query is fully assembled.
8484
*
8585
* @return
8686
* @since 3.0
8787
*/
88-
Class<? extends QueryRewriter> queryRewriter() default QueryRewriter.NoopQueryRewriter.class;
88+
Class<? extends QueryRewriter> queryRewriter() default QueryRewriter.IdentityQueryRewriter.class;
8989
}

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

+26-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2008-2022 the original author or authors.
2+
* Copyright 2022 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.
@@ -19,33 +19,41 @@
1919
import org.springframework.data.domain.Sort;
2020

2121
/**
22-
* Callback to rewrite a query right before it's handed to the EntityManager.
22+
* Callback to rewrite a query and apply sorting and pagination settings that cannot be applied based on a regularly
23+
* detectable scheme.
24+
* <p>
25+
* The underlying the query is the one right before it is used for query object creation, so everything that Spring Data
26+
* and tools intends to do has been done. You can customize the query to apply final changes. Rewriting can only make
27+
* use of already existing contextual data. That is, adding or replacing query text or reuse of bound parameters. Query
28+
* rewriting must not add additional bindable parameters as these cannot be materialized.
2329
*
2430
* @author Greg Turnquist
31+
* @author Mark Paluch
2532
* @since 3.0
33+
* @see jakarta.persistence.EntityManager#createQuery
34+
* @see jakarta.persistence.EntityManager#createNativeQuery
2635
*/
2736
@FunctionalInterface
2837
public interface QueryRewriter {
2938

3039
/**
31-
* The assembled query and current {@link Sort} settings are offered. This is the query right before it's handed to
32-
* the EntityManager, so everything that Spring Data and tools intends to do has been done. The user is able to make
33-
* any last minute changes.<br/>
34-
* <br/>
40+
* Rewrite the assembled query with the given {@link Sort}.
41+
* <p>
3542
* WARNING: No checks are performed before the transformed query is passed to the EntityManager.
36-
*
37-
* @param query - the assembled generated query, right before it's handed over to the EntityManager.
38-
* @param sort - current {@link Sort} settings provided by the method, or {@link Sort#unsorted()}} if there are none.
39-
* @return alter the query however you like.
43+
*
44+
* @param query the assembled query.
45+
* @param sort current {@link Sort} settings provided by the method, or {@link Sort#unsorted()}} if there are none.
46+
* @return the query to be used with the {@code EntityManager}.
4047
*/
4148
String rewrite(String query, Sort sort);
4249

4350
/**
44-
* This alternative is used to handle {@link Pageable}-based methods.
45-
*
46-
* @param query - the assembled generated query, right before it's handed over to the EntityManager.
47-
* @param pageRequest
48-
* @return
51+
* Rewrite the assembled query with the given {@link Pageable}.
52+
*
53+
* @param query the assembled query.
54+
* @param pageRequest current {@link Pageable} settings provided by the method, or {@link Pageable#unpaged()} if not
55+
* paged.
56+
* @return the query to be used with the {@code EntityManager}.
4957
*/
5058
default String rewrite(String query, Pageable pageRequest) {
5159
return rewrite(query, pageRequest.getSort());
@@ -54,7 +62,9 @@ default String rewrite(String query, Pageable pageRequest) {
5462
/**
5563
* A {@link QueryRewriter} that doesn't change the query.
5664
*/
57-
public class NoopQueryRewriter implements QueryRewriter {
65+
enum IdentityQueryRewriter implements QueryRewriter {
66+
67+
INSTANCE;
5868

5969
@Override
6070
public String rewrite(String query, Sort sort) {
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2008-2022 the original author or authors.
2+
* Copyright 2022 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.
@@ -19,37 +19,52 @@
1919
import jakarta.enterprise.inject.spi.Bean;
2020
import jakarta.enterprise.inject.spi.BeanManager;
2121

22-
import org.apache.commons.logging.Log;
23-
import org.apache.commons.logging.LogFactory;
22+
import java.util.Iterator;
23+
24+
import org.springframework.beans.BeanUtils;
2425
import org.springframework.data.jpa.repository.QueryRewriter;
26+
import org.springframework.data.jpa.repository.query.DelegatingQueryRewriter;
27+
import org.springframework.data.jpa.repository.query.JpaQueryMethod;
2528
import org.springframework.data.jpa.repository.query.QueryRewriterProvider;
29+
import org.springframework.data.util.Lazy;
2630

2731
/**
2832
* A {@link BeanManager}-based {@link QueryRewriterProvider}.
2933
*
3034
* @author Greg Turnquist
35+
* @author Mark Paluch
3136
* @since 3.0
3237
*/
33-
public class QueryRewriterBeanManagerProvider extends QueryRewriterProvider {
34-
35-
private static final Log LOGGER = LogFactory.getLog(QueryRewriterBeanManagerProvider.class);
38+
public class BeanManagerQueryRewriterProvider implements QueryRewriterProvider {
3639

3740
private final BeanManager beanManager;
3841

39-
public QueryRewriterBeanManagerProvider(BeanManager beanManager) {
42+
public BeanManagerQueryRewriterProvider(BeanManager beanManager) {
4043
this.beanManager = beanManager;
4144
}
4245

4346
@Override
44-
protected QueryRewriter extractQueryRewriterBean(Class<? extends QueryRewriter> queryRewriter) {
47+
@SuppressWarnings("unchecked")
48+
public QueryRewriter getQueryRewriter(JpaQueryMethod method) {
49+
50+
Class<? extends QueryRewriter> queryRewriter = method.getQueryRewriter();
51+
if (queryRewriter == QueryRewriter.IdentityQueryRewriter.class) {
52+
return QueryRewriter.IdentityQueryRewriter.INSTANCE;
53+
}
54+
55+
Iterator<Bean<?>> iterator = beanManager.getBeans(queryRewriter).iterator();
4556

46-
try {
47-
Bean<QueryRewriter> bean = (Bean<QueryRewriter>) beanManager.getBeans(queryRewriter).iterator().next();
57+
if (iterator.hasNext()) {
58+
59+
Bean<QueryRewriter> bean = (Bean<QueryRewriter>) iterator.next();
4860
CreationalContext<QueryRewriter> context = beanManager.createCreationalContext(bean);
49-
return (QueryRewriter) beanManager.getReference(bean, queryRewriter, context);
50-
} catch (Exception e) {
51-
LOGGER.error(e.toString());
52-
return null;
61+
Lazy<QueryRewriter> rewriter = Lazy
62+
.of(() -> (QueryRewriter) beanManager.getReference(bean, queryRewriter, context));
63+
64+
return new DelegatingQueryRewriter(rewriter);
5365
}
66+
67+
return BeanUtils.instantiateClass(queryRewriter);
5468
}
69+
5570
}

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/JpaRepositoryBean.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class JpaRepositoryBean<T> extends CdiRepositoryBean<T> {
6262

6363
Assert.notNull(entityManagerBean, "EntityManager bean must not be null!");
6464
this.entityManagerBean = entityManagerBean;
65-
this.queryRewriterProvider = new QueryRewriterBeanManagerProvider(beanManager);
65+
this.queryRewriterProvider = new BeanManagerQueryRewriterProvider(beanManager);
6666
}
6767

6868
@Override

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

+8-15
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,9 @@
1818
import jakarta.persistence.EntityManager;
1919
import jakarta.persistence.Query;
2020

21-
import java.util.function.Supplier;
22-
2321
import org.apache.commons.logging.Log;
2422
import org.apache.commons.logging.LogFactory;
23+
2524
import org.springframework.data.domain.Pageable;
2625
import org.springframework.data.domain.Sort;
2726
import org.springframework.data.jpa.repository.QueryRewriter;
@@ -46,14 +45,12 @@
4645
*/
4746
abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
4847

49-
private static final Log LOGGER = LogFactory.getLog(AbstractStringBasedJpaQuery.class);
50-
5148
private final DeclaredQuery query;
5249
private final DeclaredQuery countQuery;
5350
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
5451
private final SpelExpressionParser parser;
5552
private final QueryParameterSetter.QueryMetadataCache metadataCache = new QueryParameterSetter.QueryMetadataCache();
56-
private final Supplier<QueryRewriter> queryRewriterSupplier;
53+
private final QueryRewriter queryRewriter;
5754

5855
/**
5956
* Creates a new {@link AbstractStringBasedJpaQuery} from the given {@link JpaQueryMethod}, {@link EntityManager} and
@@ -65,16 +62,18 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
6562
* @param countQueryString must not be {@literal null}.
6663
* @param evaluationContextProvider must not be {@literal null}.
6764
* @param parser must not be {@literal null}.
65+
* @param queryRewriter must not be {@literal null}.
6866
*/
6967
public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,
70-
@Nullable String countQueryString, QueryMethodEvaluationContextProvider evaluationContextProvider,
71-
SpelExpressionParser parser, QueryRewriterProvider queryRewriterProvider) {
68+
@Nullable String countQueryString, QueryRewriter queryRewriter, QueryMethodEvaluationContextProvider evaluationContextProvider,
69+
SpelExpressionParser parser) {
7270

7371
super(method, em);
7472

7573
Assert.hasText(queryString, "Query string must not be null or empty!");
7674
Assert.notNull(evaluationContextProvider, "ExpressionEvaluationContextProvider must not be null!");
7775
Assert.notNull(parser, "Parser must not be null!");
76+
Assert.notNull(queryRewriter, "QueryRewriter must not be null!");
7877

7978
this.evaluationContextProvider = evaluationContextProvider;
8079
this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), parser,
@@ -85,7 +84,7 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri
8584
method.isNativeQuery());
8685

8786
this.parser = parser;
88-
this.queryRewriterSupplier = queryRewriterProvider.of(method);
87+
this.queryRewriter = queryRewriter;
8988

9089
Assert.isTrue(method.isNativeQuery() || !query.usesJdbcStyleParameters(),
9190
"JDBC style parameters (?) are not supported for JPA queries.");
@@ -169,20 +168,14 @@ protected Query createJpaQuery(String queryString, Sort sort, @Nullable Pageable
169168
/**
170169
* Use the {@link QueryRewriter}, potentially rewrite the query, using relevant {@link Sort} and {@link Pageable}
171170
* information.
172-
*
171+
*
173172
* @param originalQuery
174173
* @param sort
175174
* @param pageable
176175
* @return
177176
*/
178177
protected String potentiallyRewriteQuery(String originalQuery, Sort sort, @Nullable Pageable pageable) {
179178

180-
QueryRewriter queryRewriter = this.queryRewriterSupplier.get();
181-
182-
if (queryRewriter == null) {
183-
return originalQuery;
184-
}
185-
186179
return pageable != null && pageable.isPaged() //
187180
? queryRewriter.rewrite(originalQuery, pageable) //
188181
: queryRewriter.rewrite(originalQuery, sort);
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2008-2022 the original author or authors.
2+
* Copyright 2022 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,36 +15,38 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18-
import org.apache.commons.logging.Log;
19-
import org.apache.commons.logging.LogFactory;
20-
import org.springframework.beans.BeansException;
18+
import org.springframework.beans.BeanUtils;
2119
import org.springframework.beans.factory.BeanFactory;
2220
import org.springframework.data.jpa.repository.QueryRewriter;
21+
import org.springframework.data.util.Lazy;
2322

2423
/**
2524
* A {@link BeanFactory}-based {@link QueryRewriterProvider}.
2625
*
2726
* @author Greg Turnquist
27+
* @author Mark Paluch
2828
* @since 3.0
2929
*/
30-
public class QueryRewriterBeanFactoryProvider extends QueryRewriterProvider {
31-
32-
private static final Log LOGGER = LogFactory.getLog(QueryRewriterBeanFactoryProvider.class);
30+
public class BeanFactoryQueryRewriterProvider implements QueryRewriterProvider {
3331

3432
private final BeanFactory beanFactory;
3533

36-
public QueryRewriterBeanFactoryProvider(BeanFactory beanFactory) {
34+
public BeanFactoryQueryRewriterProvider(BeanFactory beanFactory) {
3735
this.beanFactory = beanFactory;
3836
}
3937

4038
@Override
41-
protected QueryRewriter extractQueryRewriterBean(Class<? extends QueryRewriter> queryRewriter) {
39+
@SuppressWarnings("unchecked")
40+
public QueryRewriter getQueryRewriter(JpaQueryMethod method) {
4241

43-
try {
44-
return beanFactory.getBean(queryRewriter);
45-
} catch (BeansException e) {
46-
LOGGER.error(e.toString());
47-
return null;
42+
Class<? extends QueryRewriter> queryRewriter = method.getQueryRewriter();
43+
if (queryRewriter == QueryRewriter.IdentityQueryRewriter.class) {
44+
return QueryRewriter.IdentityQueryRewriter.INSTANCE;
4845
}
46+
47+
Lazy<QueryRewriter> rewriter = Lazy.of(() -> beanFactory.getBeanProvider((Class<QueryRewriter>) queryRewriter)
48+
.getIfAvailable(() -> BeanUtils.instantiateClass(queryRewriter)));
49+
50+
return new DelegatingQueryRewriter(rewriter);
4951
}
5052
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2022 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 java.util.function.Supplier;
19+
20+
import org.springframework.data.domain.Pageable;
21+
import org.springframework.data.domain.Sort;
22+
import org.springframework.data.jpa.repository.QueryRewriter;
23+
24+
/**
25+
* Delegating {@link QueryRewriter} that delegates rewrite calls to a {@link QueryRewriter delegate} provided by a
26+
* {@link Supplier}.
27+
*
28+
* @author Mark Paluch
29+
* @since 3.0
30+
*/
31+
public class DelegatingQueryRewriter implements QueryRewriter {
32+
33+
private final Supplier<QueryRewriter> delegate;
34+
35+
public DelegatingQueryRewriter(Supplier<QueryRewriter> delegate) {
36+
this.delegate = delegate;
37+
}
38+
39+
@Override
40+
public String rewrite(String query, Sort sort) {
41+
return delegate.get().rewrite(query, sort);
42+
}
43+
44+
@Override
45+
public String rewrite(String query, Pageable pageRequest) {
46+
return delegate.get().rewrite(query, pageRequest);
47+
}
48+
}

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

+8-7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import jakarta.persistence.EntityManager;
1919

20+
import org.springframework.data.jpa.repository.QueryRewriter;
2021
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
2122
import org.springframework.data.repository.query.RepositoryQuery;
2223
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -39,20 +40,20 @@ enum JpaQueryFactory {
3940
*
4041
* @param method must not be {@literal null}.
4142
* @param em must not be {@literal null}.
42-
* @param queryString must not be {@literal null} or empty.
4343
* @param countQueryString
44+
* @param queryString must not be {@literal null}.
4445
* @param evaluationContextProvider
4546
* @return
4647
*/
4748
AbstractJpaQuery fromMethodWithQueryString(JpaQueryMethod method, EntityManager em, String queryString,
48-
@Nullable String countQueryString, QueryMethodEvaluationContextProvider evaluationContextProvider,
49-
QueryRewriterProvider queryRewriterProvider) {
49+
@Nullable String countQueryString, QueryRewriter queryRewriter,
50+
QueryMethodEvaluationContextProvider evaluationContextProvider) {
5051

5152
return method.isNativeQuery()
52-
? new NativeJpaQuery(method, em, queryString, countQueryString, evaluationContextProvider, PARSER,
53-
queryRewriterProvider)
54-
: new SimpleJpaQuery(method, em, queryString, countQueryString, evaluationContextProvider, PARSER,
55-
queryRewriterProvider);
53+
? new NativeJpaQuery(method, em, queryString, countQueryString, queryRewriter, evaluationContextProvider,
54+
PARSER)
55+
: new SimpleJpaQuery(method, em, queryString, countQueryString, queryRewriter, evaluationContextProvider,
56+
PARSER);
5657
}
5758

5859
/**

0 commit comments

Comments
 (0)