Skip to content

Commit ea1e61a

Browse files
committed
Introduce QueryRewriter.
Allow a QueryRewriter to be applied to any query crafted using @query via an additional @QueryRewriter annotation. See #2162.
1 parent fe85a33 commit ea1e61a

File tree

7 files changed

+296
-10
lines changed

7 files changed

+296
-10
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2008-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;
17+
18+
import java.lang.annotation.Documented;
19+
import java.lang.annotation.ElementType;
20+
import java.lang.annotation.Retention;
21+
import java.lang.annotation.RetentionPolicy;
22+
import java.lang.annotation.Target;
23+
24+
/**
25+
* Annotation to indicate source of {@link QueryRewriter}.
26+
*
27+
* @author Greg Turnquist
28+
* @since 3.0
29+
*/
30+
@Retention(RetentionPolicy.RUNTIME)
31+
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
32+
@Documented
33+
public @interface QueryRewrite {
34+
35+
/**
36+
* Define the {@link QueryRewriter} to callback.
37+
*/
38+
Class<?> value();
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2008-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;
17+
18+
import org.springframework.data.domain.Pageable;
19+
import org.springframework.data.domain.Sort;
20+
21+
/**
22+
* Callback to rewrite a query via {@link QueryRewrite} on a method with {@link Query}.
23+
*
24+
* @author Greg Turnquist
25+
* @since 3.0
26+
*/
27+
@FunctionalInterface
28+
public interface QueryRewriter {
29+
30+
String rewrite(String query, Sort sort);
31+
32+
default String rewrite(String query, Pageable pageRequest) {
33+
return rewrite(query, pageRequest.getSort());
34+
}
35+
}

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

+33-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
import jakarta.persistence.EntityManager;
1919
import jakarta.persistence.Query;
2020

21+
import java.lang.reflect.Constructor;
22+
import java.lang.reflect.InvocationTargetException;
23+
24+
import org.springframework.data.domain.Sort;
25+
import org.springframework.data.jpa.repository.QueryRewriter;
2126
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
2227
import org.springframework.data.repository.query.ResultProcessor;
2328
import org.springframework.data.repository.query.ReturnedType;
@@ -35,6 +40,7 @@
3540
* @author David Madden
3641
* @author Mark Paluch
3742
* @author Diego Krupitza
43+
* @author Greg Turnquist
3844
*/
3945
abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
4046

@@ -43,6 +49,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
4349
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
4450
private final SpelExpressionParser parser;
4551
private final QueryParameterSetter.QueryMetadataCache metadataCache = new QueryParameterSetter.QueryMetadataCache();
52+
private final QueryRewriter queryRewriter;
4653

4754
/**
4855
* Creates a new {@link AbstractStringBasedJpaQuery} from the given {@link JpaQueryMethod}, {@link EntityManager} and
@@ -74,6 +81,7 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri
7481
method.isNativeQuery());
7582

7683
this.parser = parser;
84+
this.queryRewriter = findQueryRewriter(method);
7785

7886
Assert.isTrue(method.isNativeQuery() || !query.usesJdbcStyleParameters(),
7987
"JDBC style parameters (?) are not supported for JPA queries.");
@@ -148,7 +156,30 @@ protected Query createJpaQuery(String queryString, ReturnedType returnedType) {
148156
Class<?> typeToRead = getTypeToRead(returnedType);
149157

150158
return typeToRead == null //
151-
? em.createQuery(queryString) //
152-
: em.createQuery(queryString, typeToRead);
159+
? em.createQuery(potentiallyRewriteQuery(queryString)) //
160+
: em.createQuery(potentiallyRewriteQuery(queryString), typeToRead);
161+
}
162+
163+
@Nullable
164+
protected QueryRewriter findQueryRewriter(JpaQueryMethod method) {
165+
166+
Class<? extends QueryRewriter> queryRewriter = method.getQueryRewriter();
167+
168+
if (queryRewriter == null) {
169+
return null;
170+
}
171+
172+
try {
173+
return (QueryRewriter) ((Constructor<?>) queryRewriter.getDeclaredConstructor()).newInstance();
174+
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
175+
System.out.println(e);
176+
return null;
177+
}
178+
}
179+
180+
protected String potentiallyRewriteQuery(String originalQuery) {
181+
return this.queryRewriter == null //
182+
? originalQuery //
183+
: this.queryRewriter.rewrite(originalQuery, Sort.unsorted());
153184
}
154185
}

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

+15-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18+
import jakarta.persistence.LockModeType;
19+
import jakarta.persistence.QueryHint;
20+
1821
import java.lang.annotation.Annotation;
1922
import java.lang.reflect.Method;
2023
import java.util.Arrays;
@@ -24,9 +27,6 @@
2427
import java.util.Optional;
2528
import java.util.Set;
2629

27-
import jakarta.persistence.LockModeType;
28-
import jakarta.persistence.QueryHint;
29-
3030
import org.springframework.core.annotation.AnnotatedElementUtils;
3131
import org.springframework.core.annotation.AnnotationUtils;
3232
import org.springframework.data.jpa.provider.QueryExtractor;
@@ -35,6 +35,8 @@
3535
import org.springframework.data.jpa.repository.Modifying;
3636
import org.springframework.data.jpa.repository.Query;
3737
import org.springframework.data.jpa.repository.QueryHints;
38+
import org.springframework.data.jpa.repository.QueryRewrite;
39+
import org.springframework.data.jpa.repository.QueryRewriter;
3840
import org.springframework.data.projection.ProjectionFactory;
3941
import org.springframework.data.repository.core.RepositoryMetadata;
4042
import org.springframework.data.repository.query.Parameter;
@@ -430,4 +432,14 @@ StoredProcedureAttributes getProcedureAttributes() {
430432
return storedProcedureAttributes;
431433
}
432434

435+
/**
436+
* Returns the {@link QueryRewriter} (if there is one). NOTE: This is only used for native (@Query) methods.
437+
*
438+
* @return
439+
* @since 3.0
440+
*/
441+
@Nullable
442+
Class<? extends QueryRewriter> getQueryRewriter() {
443+
return getMergedOrDefaultAnnotationValue("value", QueryRewrite.class, Class.class);
444+
}
433445
}

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
* @author Oliver Gierke
3636
* @author Jens Schauder
3737
* @author Mark Paluch
38+
* @author Greg Turnquist
3839
*/
3940
final class NativeJpaQuery extends AbstractStringBasedJpaQuery {
4041

@@ -65,7 +66,8 @@ protected Query createJpaQuery(String queryString, ReturnedType returnedType) {
6566
EntityManager em = getEntityManager();
6667
Class<?> type = getTypeToQueryFor(returnedType);
6768

68-
return type == null ? em.createNativeQuery(queryString) : em.createNativeQuery(queryString, type);
69+
return type == null ? em.createNativeQuery(potentiallyRewriteQuery(queryString))
70+
: em.createNativeQuery(potentiallyRewriteQuery(queryString), type);
6971
}
7072

7173
@Nullable

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java

+3-4
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,20 @@
1919
import static org.mockito.ArgumentMatchers.*;
2020
import static org.mockito.Mockito.*;
2121

22-
import java.lang.reflect.Method;
23-
import java.util.List;
24-
2522
import jakarta.persistence.EntityManager;
2623
import jakarta.persistence.EntityManagerFactory;
2724
import jakarta.persistence.metamodel.Metamodel;
2825

26+
import java.lang.reflect.Method;
27+
import java.util.List;
28+
2929
import org.junit.jupiter.api.BeforeEach;
3030
import org.junit.jupiter.api.Test;
3131
import org.junit.jupiter.api.extension.ExtendWith;
3232
import org.mockito.Mock;
3333
import org.mockito.junit.jupiter.MockitoExtension;
3434
import org.mockito.junit.jupiter.MockitoSettings;
3535
import org.mockito.quality.Strictness;
36-
3736
import org.springframework.data.domain.Page;
3837
import org.springframework.data.domain.Pageable;
3938
import org.springframework.data.domain.Sort;

0 commit comments

Comments
 (0)