diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Query.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Query.java
index f12e7c39c..614bee69c 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Query.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/Query.java
@@ -29,7 +29,7 @@ import org.springframework.data.annotation.QueryAnnotation;
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
- *
+ * @author Greg Turnquist
* @see Modifying
*/
@Retention(RetentionPolicy.RUNTIME)
@@ -45,7 +45,8 @@ public @interface Query {
/**
* Defines a special count query that shall be used for pagination queries to lookup the total number of elements for
- * a page. If none is configured we will derive the count query from the original query or {@link #countProjection()} query if any.
+ * a page. If none is configured we will derive the count query from the original query or {@link #countProjection()}
+ * query if any.
*/
String countQuery() default "";
@@ -70,11 +71,19 @@ public @interface Query {
String name() default "";
/**
- * Returns the name of the {@link jakarta.persistence.NamedQuery} to be used to execute count queries when pagination is
- * used. Will default to the named query name configured suffixed by {@code .count}.
+ * Returns the name of the {@link jakarta.persistence.NamedQuery} to be used to execute count queries when pagination
+ * is used. Will default to the named query name configured suffixed by {@code .count}.
*
* @see #name()
* @return
*/
String countName() default "";
+
+ /**
+ * Define the {@link QueryRewriter} bean that should be applied to this query after the query is full assembled.
+ *
+ * @return
+ * @since 3.0
+ */
+ Class extends QueryRewriter> queryRewriter() default QueryRewriter.NoopQueryRewriter.class;
}
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/QueryRewriter.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/QueryRewriter.java
new file mode 100644
index 000000000..d80a998c2
--- /dev/null
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/QueryRewriter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2008-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.jpa.repository;
+
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+
+/**
+ * Callback to rewrite a query right before it's handed to the EntityManager.
+ *
+ * @author Greg Turnquist
+ * @since 3.0
+ */
+@FunctionalInterface
+public interface QueryRewriter {
+
+ /**
+ * The assembled query and current {@link Sort} settings are offered. This is the query right before it's handed to
+ * the EntityManager, so everything that Spring Data and tools intends to do has been done. The user is able to make
+ * any last minute changes.
+ *
+ * WARNING: No checks are performed before the transformed query is passed to the EntityManager.
+ *
+ * @param query - the assembled generated query, right before it's handed over to the EntityManager.
+ * @param sort - current {@link Sort} settings provided by the method, or {@link Sort#unsorted()}} if there are none.
+ * @return alter the query however you like.
+ */
+ String rewrite(String query, Sort sort);
+
+ /**
+ * This alternative is used to handle {@link Pageable}-based methods.
+ *
+ * @param query - the assembled generated query, right before it's handed over to the EntityManager.
+ * @param pageRequest
+ * @return
+ */
+ default String rewrite(String query, Pageable pageRequest) {
+ return rewrite(query, pageRequest.getSort());
+ }
+
+ /**
+ * A {@link QueryRewriter} that doesn't change the query.
+ */
+ public class NoopQueryRewriter implements QueryRewriter {
+
+ @Override
+ public String rewrite(String query, Sort sort) {
+ return query;
+ }
+ }
+}
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/JpaRepositoryBean.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/JpaRepositoryBean.java
index 84dec88f9..3f9b522d2 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/JpaRepositoryBean.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/JpaRepositoryBean.java
@@ -15,18 +15,21 @@
*/
package org.springframework.data.jpa.repository.cdi;
-import java.lang.annotation.Annotation;
-import java.util.Optional;
-import java.util.Set;
-
import jakarta.enterprise.context.spi.CreationalContext;
import jakarta.enterprise.inject.spi.Bean;
import jakarta.enterprise.inject.spi.BeanManager;
import jakarta.persistence.EntityManager;
+import java.lang.annotation.Annotation;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import org.springframework.data.jpa.repository.query.QueryRewriterProvider;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.repository.cdi.CdiRepositoryBean;
import org.springframework.data.repository.config.CustomRepositoryImplementationDetector;
+import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.util.Assert;
/**
@@ -41,6 +44,7 @@ import org.springframework.util.Assert;
class JpaRepositoryBean extends CdiRepositoryBean {
private final Bean entityManagerBean;
+ private final QueryRewriterProvider queryRewriterProvider;
/**
* Constructs a {@link JpaRepositoryBean}.
@@ -58,6 +62,7 @@ class JpaRepositoryBean extends CdiRepositoryBean {
Assert.notNull(entityManagerBean, "EntityManager bean must not be null!");
this.entityManagerBean = entityManagerBean;
+ this.queryRewriterProvider = new QueryRewriterBeanManagerProvider(beanManager);
}
@Override
@@ -65,6 +70,12 @@ class JpaRepositoryBean extends CdiRepositoryBean {
EntityManager entityManager = getDependencyInstance(entityManagerBean, EntityManager.class);
- return create(() -> new JpaRepositoryFactory(entityManager), repositoryType);
+ Supplier repositoryFactorySupportSupplier = () -> {
+ JpaRepositoryFactory jpaRepositoryFactory = new JpaRepositoryFactory(entityManager);
+ jpaRepositoryFactory.setQueryRewriterProvider(queryRewriterProvider);
+ return jpaRepositoryFactory;
+ };
+
+ return create(repositoryFactorySupportSupplier, repositoryType);
}
}
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/QueryRewriterBeanManagerProvider.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/QueryRewriterBeanManagerProvider.java
new file mode 100644
index 000000000..8a4369da8
--- /dev/null
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/QueryRewriterBeanManagerProvider.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2008-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.jpa.repository.cdi;
+
+import jakarta.enterprise.context.spi.CreationalContext;
+import jakarta.enterprise.inject.spi.Bean;
+import jakarta.enterprise.inject.spi.BeanManager;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.data.jpa.repository.QueryRewriter;
+import org.springframework.data.jpa.repository.query.QueryRewriterProvider;
+
+/**
+ * A {@link BeanManager}-based {@link QueryRewriterProvider}.
+ *
+ * @author Greg Turnquist
+ * @since 3.0
+ */
+public class QueryRewriterBeanManagerProvider extends QueryRewriterProvider {
+
+ private static final Log LOGGER = LogFactory.getLog(QueryRewriterBeanManagerProvider.class);
+
+ private final BeanManager beanManager;
+
+ public QueryRewriterBeanManagerProvider(BeanManager beanManager) {
+ this.beanManager = beanManager;
+ }
+
+ @Override
+ protected QueryRewriter extractQueryRewriterBean(Class extends QueryRewriter> queryRewriter) {
+
+ try {
+ Bean bean = (Bean) beanManager.getBeans(queryRewriter).iterator().next();
+ CreationalContext context = beanManager.createCreationalContext(bean);
+ return (QueryRewriter) beanManager.getReference(bean, queryRewriter, context);
+ } catch (Exception e) {
+ LOGGER.error(e.toString());
+ return null;
+ }
+ }
+}
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java
index 2294ee18f..6f4eb5bdb 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java
@@ -18,6 +18,13 @@ package org.springframework.data.jpa.repository.query;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
+import java.util.function.Supplier;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.repository.QueryRewriter;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
@@ -35,14 +42,18 @@ import org.springframework.util.Assert;
* @author David Madden
* @author Mark Paluch
* @author Diego Krupitza
+ * @author Greg Turnquist
*/
abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
+ private static final Log LOGGER = LogFactory.getLog(AbstractStringBasedJpaQuery.class);
+
private final DeclaredQuery query;
private final DeclaredQuery countQuery;
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
private final SpelExpressionParser parser;
private final QueryParameterSetter.QueryMetadataCache metadataCache = new QueryParameterSetter.QueryMetadataCache();
+ private final Supplier queryRewriterSupplier;
/**
* Creates a new {@link AbstractStringBasedJpaQuery} from the given {@link JpaQueryMethod}, {@link EntityManager} and
@@ -57,7 +68,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
*/
public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,
@Nullable String countQueryString, QueryMethodEvaluationContextProvider evaluationContextProvider,
- SpelExpressionParser parser) {
+ SpelExpressionParser parser, QueryRewriterProvider queryRewriterProvider) {
super(method, em);
@@ -74,6 +85,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
method.isNativeQuery());
this.parser = parser;
+ this.queryRewriterSupplier = queryRewriterProvider.of(method);
Assert.isTrue(method.isNativeQuery() || !query.usesJdbcStyleParameters(),
"JDBC style parameters (?) are not supported for JPA queries.");
@@ -86,7 +98,8 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
.applySorting(accessor.getSort(), query.getAlias());
ResultProcessor processor = getQueryMethod().getResultProcessor().withDynamicProjection(accessor);
- Query query = createJpaQuery(sortedQueryString, processor.getReturnedType());
+ Query query = createJpaQuery(sortedQueryString, accessor.getSort(), accessor.getPageable(),
+ processor.getReturnedType());
QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata(sortedQueryString, query);
@@ -137,18 +150,41 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
* Creates an appropriate JPA query from an {@link EntityManager} according to the current {@link AbstractJpaQuery}
* type.
*/
- protected Query createJpaQuery(String queryString, ReturnedType returnedType) {
+ protected Query createJpaQuery(String queryString, Sort sort, @Nullable Pageable pageable,
+ ReturnedType returnedType) {
EntityManager em = getEntityManager();
if (this.query.hasConstructorExpression() || this.query.isDefaultProjection()) {
- return em.createQuery(queryString);
+ return em.createQuery(potentiallyRewriteQuery(queryString, sort, pageable));
}
Class> typeToRead = getTypeToRead(returnedType);
return typeToRead == null //
- ? em.createQuery(queryString) //
- : em.createQuery(queryString, typeToRead);
+ ? em.createQuery(potentiallyRewriteQuery(queryString, sort, pageable)) //
+ : em.createQuery(potentiallyRewriteQuery(queryString, sort, pageable), typeToRead);
+ }
+
+ /**
+ * Use the {@link QueryRewriter}, potentially rewrite the query, using relevant {@link Sort} and {@link Pageable}
+ * information.
+ *
+ * @param originalQuery
+ * @param sort
+ * @param pageable
+ * @return
+ */
+ protected String potentiallyRewriteQuery(String originalQuery, Sort sort, @Nullable Pageable pageable) {
+
+ QueryRewriter queryRewriter = this.queryRewriterSupplier.get();
+
+ if (queryRewriter == null) {
+ return originalQuery;
+ }
+
+ return pageable != null && pageable.isPaged() //
+ ? queryRewriter.rewrite(originalQuery, pageable) //
+ : queryRewriter.rewrite(originalQuery, sort);
}
}
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java
index 6eada129d..83df010e5 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java
@@ -45,12 +45,14 @@ enum JpaQueryFactory {
* @return
*/
AbstractJpaQuery fromMethodWithQueryString(JpaQueryMethod method, EntityManager em, String queryString,
- @Nullable String countQueryString,
- QueryMethodEvaluationContextProvider evaluationContextProvider) {
+ @Nullable String countQueryString, QueryMethodEvaluationContextProvider evaluationContextProvider,
+ QueryRewriterProvider queryRewriterProvider) {
return method.isNativeQuery()
- ? new NativeJpaQuery(method, em, queryString, countQueryString, evaluationContextProvider, PARSER)
- : new SimpleJpaQuery(method, em, queryString, countQueryString, evaluationContextProvider, PARSER);
+ ? new NativeJpaQuery(method, em, queryString, countQueryString, evaluationContextProvider, PARSER,
+ queryRewriterProvider)
+ : new SimpleJpaQuery(method, em, queryString, countQueryString, evaluationContextProvider, PARSER,
+ queryRewriterProvider);
}
/**
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java
index 0db5d16ba..552c587af 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java
@@ -15,13 +15,12 @@
*/
package org.springframework.data.jpa.repository.query;
-import java.lang.reflect.Method;
-
import jakarta.persistence.EntityManager;
+import java.lang.reflect.Method;
+
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.NamedQueries;
@@ -41,6 +40,7 @@ import org.springframework.util.StringUtils;
* @author Thomas Darimont
* @author Mark Paluch
* @author Réda Housni Alaoui
+ * @author Greg Turnquist
*/
public final class JpaQueryLookupStrategy {
@@ -61,6 +61,7 @@ public final class JpaQueryLookupStrategy {
private final EntityManager em;
private final JpaQueryMethodFactory queryMethodFactory;
+ private final QueryRewriterProvider queryRewriterProvider;
/**
* Creates a new {@link AbstractQueryLookupStrategy}.
@@ -68,13 +69,15 @@ public final class JpaQueryLookupStrategy {
* @param em must not be {@literal null}.
* @param queryMethodFactory must not be {@literal null}.
*/
- public AbstractQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory) {
+ public AbstractQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
+ QueryRewriterProvider queryRewriterProvider) {
Assert.notNull(em, "EntityManager must not be null!");
Assert.notNull(queryMethodFactory, "JpaQueryMethodFactory must not be null!");
this.em = em;
this.queryMethodFactory = queryMethodFactory;
+ this.queryRewriterProvider = queryRewriterProvider;
}
@Override
@@ -84,6 +87,10 @@ public final class JpaQueryLookupStrategy {
}
protected abstract RepositoryQuery resolveQuery(JpaQueryMethod method, EntityManager em, NamedQueries namedQueries);
+
+ protected QueryRewriterProvider getQueryRewriterSupplier() {
+ return queryRewriterProvider;
+ }
}
/**
@@ -97,9 +104,9 @@ public final class JpaQueryLookupStrategy {
private final EscapeCharacter escape;
public CreateQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
- EscapeCharacter escape) {
+ QueryRewriterProvider queryRewriterProvider, EscapeCharacter escape) {
- super(em, queryMethodFactory);
+ super(em, queryMethodFactory, queryRewriterProvider);
this.escape = escape;
}
@@ -130,9 +137,9 @@ public final class JpaQueryLookupStrategy {
* @param evaluationContextProvider must not be {@literal null}.
*/
public DeclaredQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
- QueryMethodEvaluationContextProvider evaluationContextProvider) {
+ QueryMethodEvaluationContextProvider evaluationContextProvider, QueryRewriterProvider queryRewriterProvider) {
- super(em, queryMethodFactory);
+ super(em, queryMethodFactory, queryRewriterProvider);
this.evaluationContextProvider = evaluationContextProvider;
}
@@ -152,14 +159,13 @@ public final class JpaQueryLookupStrategy {
}
return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, method.getRequiredAnnotatedQuery(),
- getCountQuery(method, namedQueries, em),
- evaluationContextProvider);
+ getCountQuery(method, namedQueries, em), evaluationContextProvider, getQueryRewriterSupplier());
}
String name = method.getNamedQueryName();
if (namedQueries.hasQuery(name)) {
- return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, namedQueries.getQuery(name), getCountQuery(method, namedQueries, em),
- evaluationContextProvider);
+ return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, namedQueries.getQuery(name),
+ getCountQuery(method, namedQueries, em), evaluationContextProvider, getQueryRewriterSupplier());
}
RepositoryQuery query = NamedQuery.lookupFrom(method, em);
@@ -221,9 +227,10 @@ public final class JpaQueryLookupStrategy {
* @param lookupStrategy must not be {@literal null}.
*/
public CreateIfNotFoundQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
- CreateQueryLookupStrategy createStrategy, DeclaredQueryLookupStrategy lookupStrategy) {
+ CreateQueryLookupStrategy createStrategy, DeclaredQueryLookupStrategy lookupStrategy,
+ QueryRewriterProvider queryRewriterProvider) {
- super(em, queryMethodFactory);
+ super(em, queryMethodFactory, queryRewriterProvider);
Assert.notNull(createStrategy, "CreateQueryLookupStrategy must not be null!");
Assert.notNull(lookupStrategy, "DeclaredQueryLookupStrategy must not be null!");
@@ -253,20 +260,22 @@ public final class JpaQueryLookupStrategy {
* @param escape must not be {@literal null}.
*/
public static QueryLookupStrategy create(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
- @Nullable Key key, QueryMethodEvaluationContextProvider evaluationContextProvider, EscapeCharacter escape) {
+ @Nullable Key key, QueryMethodEvaluationContextProvider evaluationContextProvider,
+ QueryRewriterProvider queryRewriterProvider, EscapeCharacter escape) {
Assert.notNull(em, "EntityManager must not be null!");
Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!");
switch (key != null ? key : Key.CREATE_IF_NOT_FOUND) {
case CREATE:
- return new CreateQueryLookupStrategy(em, queryMethodFactory, escape);
+ return new CreateQueryLookupStrategy(em, queryMethodFactory, queryRewriterProvider, escape);
case USE_DECLARED_QUERY:
- return new DeclaredQueryLookupStrategy(em, queryMethodFactory, evaluationContextProvider);
+ return new DeclaredQueryLookupStrategy(em, queryMethodFactory, evaluationContextProvider, queryRewriterProvider);
case CREATE_IF_NOT_FOUND:
return new CreateIfNotFoundQueryLookupStrategy(em, queryMethodFactory,
- new CreateQueryLookupStrategy(em, queryMethodFactory, escape),
- new DeclaredQueryLookupStrategy(em, queryMethodFactory, evaluationContextProvider));
+ new CreateQueryLookupStrategy(em, queryMethodFactory, queryRewriterProvider, escape),
+ new DeclaredQueryLookupStrategy(em, queryMethodFactory, evaluationContextProvider, queryRewriterProvider),
+ queryRewriterProvider);
default:
throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key));
}
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java
index 2c998d4db..88e085527 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java
@@ -15,6 +15,9 @@
*/
package org.springframework.data.jpa.repository.query;
+import jakarta.persistence.LockModeType;
+import jakarta.persistence.QueryHint;
+
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
@@ -24,9 +27,6 @@ import java.util.List;
import java.util.Optional;
import java.util.Set;
-import jakarta.persistence.LockModeType;
-import jakarta.persistence.QueryHint;
-
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.jpa.provider.QueryExtractor;
@@ -35,6 +35,7 @@ import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.QueryHints;
+import org.springframework.data.jpa.repository.QueryRewriter;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.Parameter;
@@ -57,6 +58,7 @@ import org.springframework.util.StringUtils;
* @author Mark Paluch
* @author Сергей Цыпанов
* @author Réda Housni Alaoui
+ * @author Greg Turnquist
*/
public class JpaQueryMethod extends QueryMethod {
@@ -430,4 +432,14 @@ public class JpaQueryMethod extends QueryMethod {
return storedProcedureAttributes;
}
+ /**
+ * Returns the {@link QueryRewriter} type.
+ *
+ * @return type of the {@link QueryRewriter}
+ * @since 3.0
+ */
+ @Nullable
+ Class extends QueryRewriter> getQueryRewriter() {
+ return getMergedOrDefaultAnnotationValue("queryRewriter", Query.class, Class.class);
+ }
}
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java
index f03866919..c517ff0ae 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java
@@ -19,6 +19,8 @@ import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import jakarta.persistence.Tuple;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.RepositoryQuery;
@@ -35,6 +37,7 @@ import org.springframework.lang.Nullable;
* @author Oliver Gierke
* @author Jens Schauder
* @author Mark Paluch
+ * @author Greg Turnquist
*/
final class NativeJpaQuery extends AbstractStringBasedJpaQuery {
@@ -48,9 +51,10 @@ final class NativeJpaQuery extends AbstractStringBasedJpaQuery {
* @param evaluationContextProvider
*/
public NativeJpaQuery(JpaQueryMethod method, EntityManager em, String queryString, @Nullable String countQueryString,
- QueryMethodEvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) {
+ QueryMethodEvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser,
+ QueryRewriterProvider queryRewriterProvider) {
- super(method, em, queryString, countQueryString, evaluationContextProvider, parser);
+ super(method, em, queryString, countQueryString, evaluationContextProvider, parser, queryRewriterProvider);
Parameters, ?> parameters = method.getParameters();
@@ -60,12 +64,13 @@ final class NativeJpaQuery extends AbstractStringBasedJpaQuery {
}
@Override
- protected Query createJpaQuery(String queryString, ReturnedType returnedType) {
+ protected Query createJpaQuery(String queryString, Sort sort, Pageable pageable, ReturnedType returnedType) {
EntityManager em = getEntityManager();
Class> type = getTypeToQueryFor(returnedType);
- return type == null ? em.createNativeQuery(queryString) : em.createNativeQuery(queryString, type);
+ return type == null ? em.createNativeQuery(potentiallyRewriteQuery(queryString, sort, pageable))
+ : em.createNativeQuery(potentiallyRewriteQuery(queryString, sort, pageable), type);
}
@Nullable
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRewriterBeanFactoryProvider.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRewriterBeanFactoryProvider.java
new file mode 100644
index 000000000..102dc6c94
--- /dev/null
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRewriterBeanFactoryProvider.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2008-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.jpa.repository.query;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.data.jpa.repository.QueryRewriter;
+
+/**
+ * A {@link BeanFactory}-based {@link QueryRewriterProvider}.
+ *
+ * @author Greg Turnquist
+ * @since 3.0
+ */
+public class QueryRewriterBeanFactoryProvider extends QueryRewriterProvider {
+
+ private static final Log LOGGER = LogFactory.getLog(QueryRewriterBeanFactoryProvider.class);
+
+ private final BeanFactory beanFactory;
+
+ public QueryRewriterBeanFactoryProvider(BeanFactory beanFactory) {
+ this.beanFactory = beanFactory;
+ }
+
+ @Override
+ protected QueryRewriter extractQueryRewriterBean(Class extends QueryRewriter> queryRewriter) {
+
+ try {
+ return beanFactory.getBean(queryRewriter);
+ } catch (BeansException e) {
+ LOGGER.error(e.toString());
+ return null;
+ }
+ }
+}
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRewriterNoopProvider.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRewriterNoopProvider.java
new file mode 100644
index 000000000..094e377cd
--- /dev/null
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRewriterNoopProvider.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2008-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.jpa.repository.query;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.data.jpa.repository.QueryRewriter;
+
+/**
+ * {@link QueryRewriterProvider} that does nothing.
+ *
+ * @author Greg Turnquist
+ * @since 3.0
+ */
+public class QueryRewriterNoopProvider extends QueryRewriterProvider {
+
+ private static final Log LOGGER = LogFactory.getLog(QueryRewriterNoopProvider.class);
+
+ /**
+ * Returns {@literal null}, signaling there is no rewriting.
+ *
+ * @param queryRewriter class definition to find in the context
+ * @return {@literal null} since this doesn't actually rewrite anything.
+ */
+ @Override
+ public QueryRewriter extractQueryRewriterBean(Class extends QueryRewriter> queryRewriter) {
+
+ LOGGER.warn("You have NOT configured JpaRepositoryFactory with a QueryRewriterProvider!");
+
+ return null;
+ }
+}
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRewriterProvider.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRewriterProvider.java
new file mode 100644
index 000000000..ba8071e5c
--- /dev/null
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRewriterProvider.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2008-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.jpa.repository.query;
+
+import java.util.function.Supplier;
+
+import org.springframework.data.jpa.repository.QueryRewriter;
+import org.springframework.lang.Nullable;
+
+/**
+ * Provide a {@link QueryRewriter} based upon the {@link JpaQueryMethod} and the surrounding context (Spring, CDI, etc.)
+ *
+ * @author Greg Turnquist
+ * @since 3.0
+ */
+public abstract class QueryRewriterProvider {
+
+ /**
+ * Using a {@link JpaQueryMethod}, extract a potential {@link QueryRewriter}. Wrap all this in a {@link Supplier} to
+ * defer the lookup until needed.
+ *
+ * @param method - JpaQueryMethod
+ * @return a {@link Supplier}-wrapped callback to fetch the {@link QueryRewriter}
+ */
+ public Supplier of(JpaQueryMethod method) {
+ return () -> findQueryRewriter(method);
+ }
+
+ /**
+ * Using the {@link org.springframework.data.jpa.repository.QueryRewrite} annotation, look for a {@link QueryRewriter}
+ * and instantiate one. NOTE: If its {@link QueryRewriter.NoopQueryRewriter}, it will just return {@literal null} and
+ * NOT do any rewrite operations.
+ *
+ * @param method - {@link JpaQueryMethod} that has the annotation details
+ * @return a {@link QueryRewriter for the method or {@code null}
+ */
+ @Nullable
+ private QueryRewriter findQueryRewriter(JpaQueryMethod method) {
+
+ Class extends QueryRewriter> queryRewriter = method.getQueryRewriter();
+
+ if (queryRewriter == null || queryRewriter == QueryRewriter.NoopQueryRewriter.class) {
+ return null;
+ }
+
+ return extractQueryRewriterBean(queryRewriter);
+ }
+
+ /**
+ * Extract an instance of {@link QueryRewriter} from the context. Implementations choose what context means, whether
+ * that is Spring, CDI, or whatever.
+ *
+ * @param queryRewriter
+ * @return a Java bean that implements {@link QueryRewriter}. {@literal null} is valid if no bean is found.
+ */
+ @Nullable
+ protected abstract QueryRewriter extractQueryRewriterBean(Class extends QueryRewriter> queryRewriter);
+}
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java
index cd0b4f228..6e48f10bd 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java
@@ -31,6 +31,7 @@ import org.springframework.lang.Nullable;
* @author Oliver Gierke
* @author Thomas Darimont
* @author Mark Paluch
+ * @author Greg Turnquist
*/
final class SimpleJpaQuery extends AbstractStringBasedJpaQuery {
@@ -44,8 +45,10 @@ final class SimpleJpaQuery extends AbstractStringBasedJpaQuery {
* @param parser must not be {@literal null}
*/
public SimpleJpaQuery(JpaQueryMethod method, EntityManager em, @Nullable String countQueryString,
- QueryMethodEvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) {
- this(method, em, method.getRequiredAnnotatedQuery(), countQueryString, evaluationContextProvider, parser);
+ QueryMethodEvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser,
+ QueryRewriterProvider queryRewriterProvider) {
+ this(method, em, method.getRequiredAnnotatedQuery(), countQueryString, evaluationContextProvider, parser,
+ queryRewriterProvider);
}
/**
@@ -59,9 +62,10 @@ final class SimpleJpaQuery extends AbstractStringBasedJpaQuery {
* @param parser must not be {@literal null}
*/
public SimpleJpaQuery(JpaQueryMethod method, EntityManager em, String queryString, @Nullable String countQueryString,
- QueryMethodEvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) {
+ QueryMethodEvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser,
+ QueryRewriterProvider queryRewriterProvider) {
- super(method, em, queryString, countQueryString, evaluationContextProvider, parser);
+ super(method, em, queryString, countQueryString, evaluationContextProvider, parser, queryRewriterProvider);
validateQuery(getQuery().getQueryString(), "Validation failed for query for method %s!", method);
diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java
index 9b6f2471e..367d0bf92 100644
--- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java
+++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java
@@ -17,29 +17,24 @@ package org.springframework.data.jpa.repository.support;
import static org.springframework.data.querydsl.QuerydslUtils.*;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.Tuple;
+
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.stream.Stream;
-import jakarta.persistence.EntityManager;
-import jakarta.persistence.Tuple;
-
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.jpa.projection.CollectionAwareProjectionFactory;
import org.springframework.data.jpa.provider.PersistenceProvider;
import org.springframework.data.jpa.provider.QueryExtractor;
import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.query.AbstractJpaQuery;
-import org.springframework.data.jpa.repository.query.DefaultJpaQueryMethodFactory;
-import org.springframework.data.jpa.repository.query.EscapeCharacter;
-import org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy;
-import org.springframework.data.jpa.repository.query.JpaQueryMethod;
-import org.springframework.data.jpa.repository.query.JpaQueryMethodFactory;
-import org.springframework.data.jpa.repository.query.Procedure;
+import org.springframework.data.jpa.repository.query.*;
import org.springframework.data.jpa.util.JpaMetamodel;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.querydsl.EntityPathResolver;
@@ -82,6 +77,7 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
private EntityPathResolver entityPathResolver;
private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT;
private JpaQueryMethodFactory queryMethodFactory;
+ private QueryRewriterProvider queryRewriterProvider;
/**
* Creates a new {@link JpaRepositoryFactory}.
@@ -98,6 +94,12 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
this.entityPathResolver = SimpleEntityPathResolver.INSTANCE;
this.queryMethodFactory = new DefaultJpaQueryMethodFactory(extractor);
+ /**
+ * Default to {@link QueryRewriterNoopProvider}. If there is a {@link BeanFactory} or {@link BeanManager}, this will
+ * result in later overriding this with the proper version.
+ */
+ this.queryRewriterProvider = new QueryRewriterNoopProvider();
+
addRepositoryProxyPostProcessor(crudMethodMetadataPostProcessor);
addRepositoryProxyPostProcessor((factory, repositoryInformation) -> {
@@ -118,6 +120,23 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
this.crudMethodMetadataPostProcessor.setBeanClassLoader(classLoader);
}
+ /**
+ * If a {@link BeanFactory} is being set, this is clearly in a Spring context, and so we can capture the
+ * {@link QueryRewriterProvider} being a {@link QueryRewriterBeanFactoryProvider}.
+ *
+ * @param beanFactory
+ * @throws BeansException
+ */
+ @Override
+ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+
+ super.setBeanFactory(beanFactory);
+
+ Assert.notNull(beanFactory, "BeanFactory must not be null!");
+
+ setQueryRewriterProvider(new QueryRewriterBeanFactoryProvider(beanFactory));
+ }
+
/**
* Configures the {@link EntityPathResolver} to be used. Defaults to {@link SimpleEntityPathResolver#INSTANCE}.
*
@@ -151,6 +170,17 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
this.queryMethodFactory = queryMethodFactory;
}
+ /**
+ * Configures the {@link QueryRewriterProvider} to be used. Defaults to {@link QueryRewriterNoopProvider}.
+ *
+ * @param queryRewriterProvider must not be {@literal null}
+ */
+ public void setQueryRewriterProvider(QueryRewriterProvider queryRewriterProvider) {
+
+ Assert.notNull(queryRewriterProvider, "QueryRewriterProvider must not be null!");
+ this.queryRewriterProvider = queryRewriterProvider;
+ }
+
@Override
protected final JpaRepositoryImplementation, ?> getTargetRepository(RepositoryInformation information) {
@@ -199,7 +229,7 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
QueryMethodEvaluationContextProvider evaluationContextProvider) {
return Optional.of(JpaQueryLookupStrategy.create(entityManager, queryMethodFactory, key, evaluationContextProvider,
- escapeCharacter));
+ queryRewriterProvider, escapeCharacter));
}
@Override
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/CdiExtensionIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/CdiExtensionIntegrationTests.java
index 5f0a69407..55f1a3021 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/CdiExtensionIntegrationTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/CdiExtensionIntegrationTests.java
@@ -24,6 +24,7 @@ import jakarta.enterprise.inject.se.SeContainer;
import jakarta.enterprise.inject.se.SeContainerInitializer;
import jakarta.enterprise.inject.spi.Bean;
+import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.apache.commons.logging.Log;
@@ -43,7 +44,7 @@ class CdiExtensionIntegrationTests {
private static Log LOGGER = LogFactory.getLog(CdiExtensionIntegrationTests.class);
@BeforeAll
- static void setUp() {
+ static void setUpCdi() {
container = SeContainerInitializer.newInstance() //
.disableDiscovery() //
@@ -53,6 +54,11 @@ class CdiExtensionIntegrationTests {
LOGGER.debug("CDI container bootstrapped!");
}
+ @AfterAll
+ static void tearDownCdi() {
+ container.close();
+ }
+
@Test // DATAJPA-319, DATAJPA-1180
@SuppressWarnings("rawtypes")
void foo() {
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/JpaQueryRewriterWithCdiIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/JpaQueryRewriterWithCdiIntegrationTests.java
new file mode 100644
index 000000000..aae03b374
--- /dev/null
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/cdi/JpaQueryRewriterWithCdiIntegrationTests.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2008-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.jpa.repository.cdi;
+
+import static org.assertj.core.api.Assertions.*;
+
+import jakarta.enterprise.context.spi.CreationalContext;
+import jakarta.enterprise.inject.se.SeContainer;
+import jakarta.enterprise.inject.se.SeContainerInitializer;
+import jakarta.enterprise.inject.spi.Bean;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.sample.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.jpa.repository.QueryRewriter;
+import org.springframework.data.repository.cdi.Eager;
+
+/**
+ * Unit tests for repository with {@link Query} and {@link QueryRewrite} in a CDI environment.
+ *
+ * @author Greg Turnquist
+ */
+public class JpaQueryRewriterWithCdiIntegrationTests {
+
+ private static SeContainer container;
+ private static Log LOGGER = LogFactory.getLog(CdiExtensionIntegrationTests.class);
+
+ private UserRepositoryWithRewriter repository;
+
+ // Results
+ static final String ORIGINAL_QUERY = "original query";
+ static final String REWRITTEN_QUERY = "rewritten query";
+ static final String SORT = "sort";
+ static Map results = new HashMap<>();
+
+ @BeforeAll
+ static void setUpCdi() {
+
+ container = SeContainerInitializer.newInstance() //
+ .disableDiscovery() //
+ .addPackages(UserRepositoryWithRewriter.class) //
+ .initialize();
+
+ LOGGER.debug("CDI container bootstrapped!");
+ }
+
+ @AfterAll
+ static void tearDownCdi() {
+ container.close();
+ }
+
+ @BeforeEach
+ void setUp() {
+
+ Bean> repositoryBean = container.getBeanManager().getBeans(UserRepositoryWithRewriter.class).iterator().next();
+ CreationalContext> context = container.getBeanManager().createCreationalContext(repositoryBean);
+ this.repository = (UserRepositoryWithRewriter) container.getBeanManager().getReference(repositoryBean,
+ UserRepositoryWithRewriter.class, context);
+
+ results.clear();
+ }
+
+ @Test
+ void nativeQueryShouldHandleRewrites() throws NoSuchMethodException {
+
+ repository.findByNativeQuery("Matthews");
+
+ assertThat(results).containsExactly( //
+ entry(ORIGINAL_QUERY, "select original_user_alias.* from SD_USER original_user_alias"), //
+ entry(REWRITTEN_QUERY, "select rewritten_user_alias.* from SD_USER rewritten_user_alias"), //
+ entry(SORT, Sort.unsorted().toString()));
+ }
+
+ @Test
+ void nonNativeQueryShouldHandleRewrites() throws NoSuchMethodException {
+
+ repository.findByNonNativeQuery("Matthews");
+
+ assertThat(results).containsExactly( //
+ entry(ORIGINAL_QUERY, "select original_user_alias from User original_user_alias"), //
+ entry(REWRITTEN_QUERY, "select rewritten_user_alias from User rewritten_user_alias"), //
+ entry(SORT, Sort.unsorted().toString()));
+ }
+
+ @Test
+ void nonNativeQueryWithSortShouldHandleRewrites() throws NoSuchMethodException {
+
+ repository.findByNonNativeSortedQuery("Matthews", Sort.by("lastname"));
+
+ assertThat(results).containsExactly( //
+ entry(ORIGINAL_QUERY,
+ "select original_user_alias from User original_user_alias order by original_user_alias.lastname asc"), //
+ entry(REWRITTEN_QUERY,
+ "select rewritten_user_alias from User rewritten_user_alias order by rewritten_user_alias.lastname asc"), //
+ entry(SORT, Sort.by("lastname").ascending().toString()));
+
+ repository.findByNonNativeSortedQuery("Matthews", Sort.by("firstname").descending());
+
+ assertThat(results).containsExactly( //
+ entry(ORIGINAL_QUERY,
+ "select original_user_alias from User original_user_alias order by original_user_alias.firstname desc"), //
+ entry(REWRITTEN_QUERY,
+ "select rewritten_user_alias from User rewritten_user_alias order by rewritten_user_alias.firstname desc"), //
+ entry(SORT, Sort.by("firstname").descending().toString()));
+ }
+
+ @Test
+ void nonNativeQueryWithPageableShouldHandleRewrites() throws NoSuchMethodException {
+
+ repository.findByNonNativePagedQuery("Matthews", PageRequest.of(2, 1));
+
+ assertThat(results).containsExactly( //
+ entry(ORIGINAL_QUERY, "select original_user_alias from User original_user_alias"), //
+ entry(REWRITTEN_QUERY, "select rewritten_user_alias from User rewritten_user_alias"), //
+ entry(SORT, Sort.unsorted().toString()));
+ }
+
+ @Test
+ void nativeQueryWithNoRewriteAnnotationShouldNotDoRewrites() throws NoSuchMethodException {
+
+ repository.findByNativeQueryWithNoRewrite("Matthews");
+
+ assertThat(results).isEmpty();
+ }
+
+ @Test
+ void nonNativeQueryWithNoRewriteAnnotationShouldNotDoRewrites() throws NoSuchMethodException {
+
+ repository.findByNonNativeQueryWithNoRewrite("Matthews");
+
+ assertThat(results).isEmpty();
+ }
+
+ @Test
+ void nativeQueryShouldHandleRewritesUsingRepositoryRewriter() throws NoSuchMethodException {
+
+ repository.findByNativeQueryUsingRepository("Matthews");
+
+ assertThat(results).containsExactly( //
+ entry(ORIGINAL_QUERY, "select original_user_alias.* from SD_USER original_user_alias"), //
+ entry(REWRITTEN_QUERY, "select rewritten_user_alias.* from SD_USER rewritten_user_alias"), //
+ entry(SORT, Sort.unsorted().toString()));
+ }
+
+ /**
+ * {@link QueryRewriter} implemented by the repository.
+ */
+ @Eager
+ public interface UserRepositoryWithRewriter extends JpaRepository, QueryRewriter {
+
+ @Query(value = "select original_user_alias.* from SD_USER original_user_alias", nativeQuery = true,
+ queryRewriter = TestQueryRewriter.class)
+ List findByNativeQuery(String param);
+
+ @Query(value = "select original_user_alias from User original_user_alias", queryRewriter = TestQueryRewriter.class)
+ List findByNonNativeQuery(String param);
+
+ @Query(value = "select original_user_alias from User original_user_alias", queryRewriter = TestQueryRewriter.class)
+ List findByNonNativeSortedQuery(String param, Sort sort);
+
+ @Query(value = "select original_user_alias from User original_user_alias", queryRewriter = TestQueryRewriter.class)
+ List findByNonNativePagedQuery(String param, Pageable pageable);
+
+ @Query(value = "select original_user_alias.* from SD_USER original_user_alias", nativeQuery = true)
+ List findByNativeQueryWithNoRewrite(String param);
+
+ @Query(value = "select original_user_alias from User original_user_alias")
+ List findByNonNativeQueryWithNoRewrite(String param);
+
+ @Query(value = "select original_user_alias.* from SD_USER original_user_alias", nativeQuery = true,
+ queryRewriter = UserRepositoryWithRewriter.class)
+ List findByNativeQueryUsingRepository(String param);
+
+ @Override
+ default String rewrite(String query, Sort sort) {
+ return replaceAlias(query, sort);
+ }
+ }
+
+ /**
+ * Stand-alone {@link QueryRewriter}.
+ */
+ static class TestQueryRewriter implements QueryRewriter {
+
+ @Override
+ public String rewrite(String query, Sort sort) {
+ return replaceAlias(query, sort);
+ }
+ }
+
+ /**
+ * One query rewriter function to rule them all!
+ *
+ * @param query
+ * @param sort
+ */
+ private static String replaceAlias(String query, Sort sort) {
+
+ String rewrittenQuery = query.replaceAll("original_user_alias", "rewritten_user_alias");
+
+ // Capture results for testing.
+ results.put(ORIGINAL_QUERY, query);
+ results.put(REWRITTEN_QUERY, rewrittenQuery);
+ results.put(SORT, sort.toString());
+
+ return rewrittenQuery;
+ }
+}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java
index a7254bc96..09f7789b9 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryIntegrationTests.java
@@ -18,19 +18,22 @@ package org.springframework.data.jpa.repository.query;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
-import java.lang.reflect.Method;
-import java.util.Set;
-
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Tuple;
+import java.lang.reflect.Method;
+import java.util.Set;
+
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
-
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.sample.Role;
import org.springframework.data.jpa.domain.sample.User;
import org.springframework.data.jpa.provider.PersistenceProvider;
+import org.springframework.data.jpa.repository.Query;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
@@ -43,6 +46,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
* Integration tests for {@link AbstractStringBasedJpaQuery}.
*
* @author Oliver Gierke
+ * @author Greg Turnquist
* @soundtrack Henrik Freischlader Trio - Nobody Else To Blame (Openness)
*/
@ExtendWith(SpringExtension.class)
@@ -51,6 +55,8 @@ public class AbstractStringBasedJpaQueryIntegrationTests {
@PersistenceContext EntityManager em;
+ @Autowired BeanFactory beanFactory;
+
@Test // DATAJPA-885
void createsNormalQueryForJpaManagedReturnTypes() throws Exception {
@@ -60,10 +66,12 @@ public class AbstractStringBasedJpaQueryIntegrationTests {
when(mock.getMetamodel()).thenReturn(em.getMetamodel());
JpaQueryMethod method = getMethod("findRolesByEmailAddress", String.class);
- AbstractStringBasedJpaQuery jpaQuery = new SimpleJpaQuery(method, mock,
- null, QueryMethodEvaluationContextProvider.DEFAULT, new SpelExpressionParser());
+ AbstractStringBasedJpaQuery jpaQuery = new SimpleJpaQuery(method, mock, null,
+ QueryMethodEvaluationContextProvider.DEFAULT, new SpelExpressionParser(),
+ new QueryRewriterBeanFactoryProvider(beanFactory));
- jpaQuery.createJpaQuery(method.getAnnotatedQuery(), method.getResultProcessor().getReturnedType());
+ jpaQuery.createJpaQuery(method.getAnnotatedQuery(), Sort.unsorted(), null,
+ method.getResultProcessor().getReturnedType());
verify(mock, times(1)).createQuery(anyString());
verify(mock, times(0)).createQuery(anyString(), eq(Tuple.class));
@@ -80,7 +88,7 @@ public class AbstractStringBasedJpaQueryIntegrationTests {
interface SampleRepository extends Repository {
- @org.springframework.data.jpa.repository.Query("select u.roles from User u where u.emailAddress = ?1")
+ @Query("select u.roles from User u where u.emailAddress = ?1")
Set findRolesByEmailAddress(String emailAddress);
}
}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java
index 187df77ea..ed71fc826 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategyUnitTests.java
@@ -19,13 +19,13 @@ import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
-import java.lang.reflect.Method;
-import java.util.List;
-
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.metamodel.Metamodel;
+import java.lang.reflect.Method;
+import java.util.List;
+
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -33,7 +33,7 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
-
+import org.springframework.beans.factory.BeanFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
@@ -57,6 +57,7 @@ import org.springframework.data.repository.query.RepositoryQuery;
* @author Thomas Darimont
* @author Jens Schauder
* @author Réda Housni Alaoui
+ * @author Greg Turnquist
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@@ -70,6 +71,7 @@ public class JpaQueryLookupStrategyUnitTests {
@Mock NamedQueries namedQueries;
@Mock Metamodel metamodel;
@Mock ProjectionFactory projectionFactory;
+ @Mock BeanFactory beanFactory;
private JpaQueryMethodFactory queryMethodFactory;
@@ -87,7 +89,7 @@ public class JpaQueryLookupStrategyUnitTests {
void invalidAnnotatedQueryCausesException() throws Exception {
QueryLookupStrategy strategy = JpaQueryLookupStrategy.create(em, queryMethodFactory, Key.CREATE_IF_NOT_FOUND,
- EVALUATION_CONTEXT_PROVIDER, EscapeCharacter.DEFAULT);
+ EVALUATION_CONTEXT_PROVIDER, new QueryRewriterBeanFactoryProvider(beanFactory), EscapeCharacter.DEFAULT);
Method method = UserRepository.class.getMethod("findByFoo", String.class);
RepositoryMetadata metadata = new DefaultRepositoryMetadata(UserRepository.class);
@@ -103,7 +105,7 @@ public class JpaQueryLookupStrategyUnitTests {
void sholdThrowMorePreciseExceptionIfTryingToUsePaginationInNativeQueries() throws Exception {
QueryLookupStrategy strategy = JpaQueryLookupStrategy.create(em, queryMethodFactory, Key.CREATE_IF_NOT_FOUND,
- EVALUATION_CONTEXT_PROVIDER, EscapeCharacter.DEFAULT);
+ EVALUATION_CONTEXT_PROVIDER, new QueryRewriterBeanFactoryProvider(beanFactory), EscapeCharacter.DEFAULT);
Method method = UserRepository.class.getMethod("findByInvalidNativeQuery", String.class, Sort.class);
RepositoryMetadata metadata = new DefaultRepositoryMetadata(UserRepository.class);
@@ -117,7 +119,7 @@ public class JpaQueryLookupStrategyUnitTests {
void considersNamedCountQuery() throws Exception {
QueryLookupStrategy strategy = JpaQueryLookupStrategy.create(em, queryMethodFactory, Key.CREATE_IF_NOT_FOUND,
- EVALUATION_CONTEXT_PROVIDER, EscapeCharacter.DEFAULT);
+ EVALUATION_CONTEXT_PROVIDER, new QueryRewriterBeanFactoryProvider(beanFactory), EscapeCharacter.DEFAULT);
when(namedQueries.hasQuery("foo.count")).thenReturn(true);
when(namedQueries.getQuery("foo.count")).thenReturn("foo count");
@@ -139,7 +141,7 @@ public class JpaQueryLookupStrategyUnitTests {
void considersNamedCountOnStringQueryQuery() throws Exception {
QueryLookupStrategy strategy = JpaQueryLookupStrategy.create(em, queryMethodFactory, Key.CREATE_IF_NOT_FOUND,
- EVALUATION_CONTEXT_PROVIDER, EscapeCharacter.DEFAULT);
+ EVALUATION_CONTEXT_PROVIDER, new QueryRewriterBeanFactoryProvider(beanFactory), EscapeCharacter.DEFAULT);
when(namedQueries.hasQuery("foo.count")).thenReturn(true);
when(namedQueries.getQuery("foo.count")).thenReturn("foo count");
@@ -158,7 +160,7 @@ public class JpaQueryLookupStrategyUnitTests {
void prefersDeclaredQuery() throws Exception {
QueryLookupStrategy strategy = JpaQueryLookupStrategy.create(em, queryMethodFactory, Key.CREATE_IF_NOT_FOUND,
- EVALUATION_CONTEXT_PROVIDER, EscapeCharacter.DEFAULT);
+ EVALUATION_CONTEXT_PROVIDER, new QueryRewriterBeanFactoryProvider(beanFactory), EscapeCharacter.DEFAULT);
Method method = UserRepository.class.getMethod("annotatedQueryWithQueryAndQueryName");
RepositoryMetadata metadata = new DefaultRepositoryMetadata(UserRepository.class);
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryRewriteIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryRewriteIntegrationTests.java
new file mode 100644
index 000000000..f11d645fc
--- /dev/null
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryRewriteIntegrationTests.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2008-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.jpa.repository.query;
+
+import static org.assertj.core.api.Assertions.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.context.annotation.ImportResource;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.sample.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.jpa.repository.QueryRewriter;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+/**
+ * Unit tests for repository with {@link Query} and {@link QueryRewrite}.
+ *
+ * @author Greg Turnquist
+ */
+@ExtendWith(SpringExtension.class)
+@ContextConfiguration
+public class JpaQueryRewriteIntegrationTests {
+
+ @Autowired private UserRepositoryWithRewriter repository;
+
+ // Results
+ static final String ORIGINAL_QUERY = "original query";
+ static final String REWRITTEN_QUERY = "rewritten query";
+ static final String SORT = "sort";
+ static Map results = new HashMap<>();
+
+ @BeforeEach
+ void setUp() {
+ results.clear();
+ }
+
+ @Test
+ void nativeQueryShouldHandleRewrites() throws NoSuchMethodException {
+
+ repository.findByNativeQuery("Matthews");
+
+ assertThat(results).containsExactly( //
+ entry(ORIGINAL_QUERY, "select original_user_alias.* from SD_USER original_user_alias"), //
+ entry(REWRITTEN_QUERY, "select rewritten_user_alias.* from SD_USER rewritten_user_alias"), //
+ entry(SORT, Sort.unsorted().toString()));
+ }
+
+ @Test
+ void nonNativeQueryShouldHandleRewrites() throws NoSuchMethodException {
+
+ repository.findByNonNativeQuery("Matthews");
+
+ assertThat(results).containsExactly( //
+ entry(ORIGINAL_QUERY, "select original_user_alias from User original_user_alias"), //
+ entry(REWRITTEN_QUERY, "select rewritten_user_alias from User rewritten_user_alias"), //
+ entry(SORT, Sort.unsorted().toString()));
+ }
+
+ @Test
+ void nonNativeQueryWithSortShouldHandleRewrites() throws NoSuchMethodException {
+
+ repository.findByNonNativeSortedQuery("Matthews", Sort.by("lastname"));
+
+ assertThat(results).containsExactly( //
+ entry(ORIGINAL_QUERY,
+ "select original_user_alias from User original_user_alias order by original_user_alias.lastname asc"), //
+ entry(REWRITTEN_QUERY,
+ "select rewritten_user_alias from User rewritten_user_alias order by rewritten_user_alias.lastname asc"), //
+ entry(SORT, Sort.by("lastname").ascending().toString()));
+
+ repository.findByNonNativeSortedQuery("Matthews", Sort.by("firstname").descending());
+
+ assertThat(results).containsExactly( //
+ entry(ORIGINAL_QUERY,
+ "select original_user_alias from User original_user_alias order by original_user_alias.firstname desc"), //
+ entry(REWRITTEN_QUERY,
+ "select rewritten_user_alias from User rewritten_user_alias order by rewritten_user_alias.firstname desc"), //
+ entry(SORT, Sort.by("firstname").descending().toString()));
+ }
+
+ @Test
+ void nonNativeQueryWithPageableShouldHandleRewrites() throws NoSuchMethodException {
+
+ repository.findByNonNativePagedQuery("Matthews", PageRequest.of(2, 1));
+
+ assertThat(results).containsExactly( //
+ entry(ORIGINAL_QUERY, "select original_user_alias from User original_user_alias"), //
+ entry(REWRITTEN_QUERY, "select rewritten_user_alias from User rewritten_user_alias"), //
+ entry(SORT, Sort.unsorted().toString()));
+ }
+
+ @Test
+ void nativeQueryWithNoRewriteAnnotationShouldNotDoRewrites() throws NoSuchMethodException {
+
+ repository.findByNativeQueryWithNoRewrite("Matthews");
+
+ assertThat(results).isEmpty();
+ }
+
+ @Test
+ void nonNativeQueryWithNoRewriteAnnotationShouldNotDoRewrites() throws NoSuchMethodException {
+
+ repository.findByNonNativeQueryWithNoRewrite("Matthews");
+
+ assertThat(results).isEmpty();
+ }
+
+ @Test
+ void nativeQueryShouldHandleRewritesUsingRepositoryRewriter() throws NoSuchMethodException {
+
+ repository.findByNativeQueryUsingRepository("Matthews");
+
+ assertThat(results).containsExactly( //
+ entry(ORIGINAL_QUERY, "select original_user_alias.* from SD_USER original_user_alias"), //
+ entry(REWRITTEN_QUERY, "select rewritten_user_alias.* from SD_USER rewritten_user_alias"), //
+ entry(SORT, Sort.unsorted().toString()));
+ }
+
+ public interface UserRepositoryWithRewriter extends JpaRepository, QueryRewriter {
+
+ @Query(value = "select original_user_alias.* from SD_USER original_user_alias", nativeQuery = true,
+ queryRewriter = TestQueryRewriter.class)
+ List findByNativeQuery(String param);
+
+ @Query(value = "select original_user_alias from User original_user_alias", queryRewriter = TestQueryRewriter.class)
+ List findByNonNativeQuery(String param);
+
+ @Query(value = "select original_user_alias from User original_user_alias", queryRewriter = TestQueryRewriter.class)
+ List findByNonNativeSortedQuery(String param, Sort sort);
+
+ @Query(value = "select original_user_alias from User original_user_alias", queryRewriter = TestQueryRewriter.class)
+ List findByNonNativePagedQuery(String param, Pageable pageable);
+
+ @Query(value = "select original_user_alias.* from SD_USER original_user_alias", nativeQuery = true)
+ List findByNativeQueryWithNoRewrite(String param);
+
+ @Query(value = "select original_user_alias from User original_user_alias")
+ List findByNonNativeQueryWithNoRewrite(String param);
+
+ @Query(value = "select original_user_alias.* from SD_USER original_user_alias", nativeQuery = true,
+ queryRewriter = UserRepositoryWithRewriter.class)
+ List findByNativeQueryUsingRepository(String param);
+
+ @Override
+ default String rewrite(String query, Sort sort) {
+ return replaceAlias(query, sort);
+ }
+ }
+
+ static class TestQueryRewriter implements QueryRewriter {
+
+ @Override
+ public String rewrite(String query, Sort sort) {
+ return replaceAlias(query, sort);
+ }
+ }
+
+ /**
+ * One query rewriter function to rule them all!
+ *
+ * @param query
+ * @param sort
+ */
+ private static String replaceAlias(String query, Sort sort) {
+
+ String rewrittenQuery = query.replaceAll("original_user_alias", "rewritten_user_alias");
+
+ // Capture results for testing.
+ results.put(ORIGINAL_QUERY, query);
+ results.put(REWRITTEN_QUERY, rewrittenQuery);
+ results.put(SORT, sort.toString());
+
+ return rewrittenQuery;
+ }
+
+ @Configuration
+ @ImportResource("classpath:infrastructure.xml")
+ @EnableJpaRepositories(considerNestedRepositories = true, basePackageClasses = UserRepositoryWithRewriter.class, //
+ includeFilters = @ComponentScan.Filter(value = { UserRepositoryWithRewriter.class },
+ type = FilterType.ASSIGNABLE_TYPE))
+ static class JpaRepositoryConfig {
+
+ @Bean
+ QueryRewriter queryRewriter() {
+ return new TestQueryRewriter();
+ }
+
+ }
+}
diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java
index 077b0474f..d394bfc5c 100644
--- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java
+++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java
@@ -19,16 +19,16 @@ import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
-import java.lang.reflect.Method;
-import java.util.Collection;
-import java.util.List;
-
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Tuple;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.metamodel.Metamodel;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.List;
+
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -37,7 +37,7 @@ import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
-
+import org.springframework.beans.factory.BeanFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
@@ -62,6 +62,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
* @author Jens Schauder
* @author Tom Hombergs
* @author Mark Paluch
+ * @author Greg Turnquist
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
@@ -81,6 +82,7 @@ class SimpleJpaQueryUnitTests {
@Mock RepositoryMetadata metadata;
@Mock ParameterBinder binder;
@Mock Metamodel metamodel;
+ @Mock BeanFactory beanFactory;
private ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
@@ -107,12 +109,12 @@ class SimpleJpaQueryUnitTests {
void prefersDeclaredCountQueryOverCreatingOne() throws Exception {
method = new JpaQueryMethod(
- SimpleJpaQueryUnitTests.class.getDeclaredMethod("prefersDeclaredCountQueryOverCreatingOne"),
- metadata, factory, extractor);
+ SimpleJpaQueryUnitTests.class.getDeclaredMethod("prefersDeclaredCountQueryOverCreatingOne"), metadata, factory,
+ extractor);
when(em.createQuery("foo", Long.class)).thenReturn(typedQuery);
SimpleJpaQuery jpaQuery = new SimpleJpaQuery(method, em, "select u from User u", null, EVALUATION_CONTEXT_PROVIDER,
- PARSER);
+ PARSER, new QueryRewriterBeanFactoryProvider(beanFactory));
assertThat(jpaQuery.createCountQuery(new JpaParametersParameterAccessor(method.getParameters(), new Object[] {})))
.isEqualTo(typedQuery);
@@ -127,8 +129,7 @@ class SimpleJpaQueryUnitTests {
JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, factory, extractor);
AbstractJpaQuery jpaQuery = new SimpleJpaQuery(queryMethod, em, "select u from User u", null,
- EVALUATION_CONTEXT_PROVIDER,
- PARSER);
+ EVALUATION_CONTEXT_PROVIDER, PARSER, new QueryRewriterBeanFactoryProvider(beanFactory));
jpaQuery.createCountQuery(
new JpaParametersParameterAccessor(queryMethod.getParameters(), new Object[] { PageRequest.of(1, 10) }));
@@ -143,7 +144,7 @@ class SimpleJpaQueryUnitTests {
Method method = SampleRepository.class.getMethod("findNativeByLastname", String.class);
JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, factory, extractor);
AbstractJpaQuery jpaQuery = JpaQueryFactory.INSTANCE.fromMethodWithQueryString(queryMethod, em,
- queryMethod.getAnnotatedQuery(), null, EVALUATION_CONTEXT_PROVIDER);
+ queryMethod.getAnnotatedQuery(), null, EVALUATION_CONTEXT_PROVIDER, new QueryRewriterBeanFactoryProvider(beanFactory));
assertThat(jpaQuery instanceof NativeJpaQuery).isTrue();
@@ -246,8 +247,7 @@ class SimpleJpaQueryUnitTests {
JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, factory, extractor);
AbstractJpaQuery jpaQuery = new SimpleJpaQuery(queryMethod, em, "select u from User u",
- "select count(u.id) from #{#entityName} u", EVALUATION_CONTEXT_PROVIDER,
- PARSER);
+ "select count(u.id) from #{#entityName} u", EVALUATION_CONTEXT_PROVIDER, PARSER, new QueryRewriterBeanFactoryProvider(beanFactory));
jpaQuery.createCountQuery(
new JpaParametersParameterAccessor(queryMethod.getParameters(), new Object[] { PageRequest.of(1, 10) }));
@@ -259,7 +259,7 @@ class SimpleJpaQueryUnitTests {
JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, factory, extractor);
return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(queryMethod, em, queryMethod.getAnnotatedQuery(), null,
- EVALUATION_CONTEXT_PROVIDER);
+ EVALUATION_CONTEXT_PROVIDER, new QueryRewriterBeanFactoryProvider(beanFactory));
}
interface SampleRepository {
diff --git a/src/main/asciidoc/jpa.adoc b/src/main/asciidoc/jpa.adoc
index 7fd3d8bfa..664f72e61 100644
--- a/src/main/asciidoc/jpa.adoc
+++ b/src/main/asciidoc/jpa.adoc
@@ -293,7 +293,7 @@ public class User {
[[jpa.query-methods.named-queries.declaring-interfaces]]
==== Declaring Interfaces
-To allow these named queries, specify the `UserRepository` as follows:
+To allow these named queries, specify the `UserRepositoryWithRewriter` as follows:
.Query method declaration in UserRepository
====