Introduce QueryRewriter.
Allow a QueryRewriter to be applied to any query crafted using @Query via an additional @QueryRewriter annotation. Also supported directly inside @Query. See #2162.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.<br/>
|
||||
* <br/>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<T> extends CdiRepositoryBean<T> {
|
||||
|
||||
private final Bean<EntityManager> entityManagerBean;
|
||||
private final QueryRewriterProvider queryRewriterProvider;
|
||||
|
||||
/**
|
||||
* Constructs a {@link JpaRepositoryBean}.
|
||||
@@ -58,6 +62,7 @@ class JpaRepositoryBean<T> extends CdiRepositoryBean<T> {
|
||||
|
||||
Assert.notNull(entityManagerBean, "EntityManager bean must not be null!");
|
||||
this.entityManagerBean = entityManagerBean;
|
||||
this.queryRewriterProvider = new QueryRewriterBeanManagerProvider(beanManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,6 +70,12 @@ class JpaRepositoryBean<T> extends CdiRepositoryBean<T> {
|
||||
|
||||
EntityManager entityManager = getDependencyInstance(entityManagerBean, EntityManager.class);
|
||||
|
||||
return create(() -> new JpaRepositoryFactory(entityManager), repositoryType);
|
||||
Supplier<RepositoryFactorySupport> repositoryFactorySupportSupplier = () -> {
|
||||
JpaRepositoryFactory jpaRepositoryFactory = new JpaRepositoryFactory(entityManager);
|
||||
jpaRepositoryFactory.setQueryRewriterProvider(queryRewriterProvider);
|
||||
return jpaRepositoryFactory;
|
||||
};
|
||||
|
||||
return create(repositoryFactorySupportSupplier, repositoryType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<QueryRewriter> bean = (Bean<QueryRewriter>) beanManager.getBeans(queryRewriter).iterator().next();
|
||||
CreationalContext<QueryRewriter> context = beanManager.createCreationalContext(bean);
|
||||
return (QueryRewriter) beanManager.getReference(bean, queryRewriter, context);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<QueryRewriter> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<QueryRewriter> 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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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<String, String> 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<User, Integer>, QueryRewriter {
|
||||
|
||||
@Query(value = "select original_user_alias.* from SD_USER original_user_alias", nativeQuery = true,
|
||||
queryRewriter = TestQueryRewriter.class)
|
||||
List<User> findByNativeQuery(String param);
|
||||
|
||||
@Query(value = "select original_user_alias from User original_user_alias", queryRewriter = TestQueryRewriter.class)
|
||||
List<User> findByNonNativeQuery(String param);
|
||||
|
||||
@Query(value = "select original_user_alias from User original_user_alias", queryRewriter = TestQueryRewriter.class)
|
||||
List<User> findByNonNativeSortedQuery(String param, Sort sort);
|
||||
|
||||
@Query(value = "select original_user_alias from User original_user_alias", queryRewriter = TestQueryRewriter.class)
|
||||
List<User> findByNonNativePagedQuery(String param, Pageable pageable);
|
||||
|
||||
@Query(value = "select original_user_alias.* from SD_USER original_user_alias", nativeQuery = true)
|
||||
List<User> findByNativeQueryWithNoRewrite(String param);
|
||||
|
||||
@Query(value = "select original_user_alias from User original_user_alias")
|
||||
List<User> findByNonNativeQueryWithNoRewrite(String param);
|
||||
|
||||
@Query(value = "select original_user_alias.* from SD_USER original_user_alias", nativeQuery = true,
|
||||
queryRewriter = UserRepositoryWithRewriter.class)
|
||||
List<User> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<User, Integer> {
|
||||
|
||||
@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<Role> findRolesByEmailAddress(String emailAddress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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<String, String> 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<User, Integer>, QueryRewriter {
|
||||
|
||||
@Query(value = "select original_user_alias.* from SD_USER original_user_alias", nativeQuery = true,
|
||||
queryRewriter = TestQueryRewriter.class)
|
||||
List<User> findByNativeQuery(String param);
|
||||
|
||||
@Query(value = "select original_user_alias from User original_user_alias", queryRewriter = TestQueryRewriter.class)
|
||||
List<User> findByNonNativeQuery(String param);
|
||||
|
||||
@Query(value = "select original_user_alias from User original_user_alias", queryRewriter = TestQueryRewriter.class)
|
||||
List<User> findByNonNativeSortedQuery(String param, Sort sort);
|
||||
|
||||
@Query(value = "select original_user_alias from User original_user_alias", queryRewriter = TestQueryRewriter.class)
|
||||
List<User> findByNonNativePagedQuery(String param, Pageable pageable);
|
||||
|
||||
@Query(value = "select original_user_alias.* from SD_USER original_user_alias", nativeQuery = true)
|
||||
List<User> findByNativeQueryWithNoRewrite(String param);
|
||||
|
||||
@Query(value = "select original_user_alias from User original_user_alias")
|
||||
List<User> findByNonNativeQueryWithNoRewrite(String param);
|
||||
|
||||
@Query(value = "select original_user_alias.* from SD_USER original_user_alias", nativeQuery = true,
|
||||
queryRewriter = UserRepositoryWithRewriter.class)
|
||||
List<User> 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
====
|
||||
|
||||
Reference in New Issue
Block a user