Introduce QueryEnhancerSelector to configure which QueryEnhancerFactory to use.
Introduce QueryEnhancerSelector to EnableJpaRepositories. Also, split DeclaredQuery into two interfaces to resolve the inner cycle of query introspection while just a value object is being created. Introduce JpaQueryConfiguration to capture a multitude of configuration elements. Remove `spring.data.jpa.query.native.parser` option introduced earlier with #2989 Closes #3622 Original pull request: #3527
This commit is contained in:
@@ -55,8 +55,8 @@ public class HqlParserBenchmarks {
|
||||
OR p.description LIKE "cost overrun"
|
||||
""";
|
||||
|
||||
query = DeclaredQuery.of(s, false);
|
||||
enhancer = QueryEnhancerFactory.forQuery(query);
|
||||
query = DeclaredQuery.ofJpql(s);
|
||||
enhancer = QueryEnhancerFactory.forQuery(query).create(query);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ public class JSqlParserQueryEnhancerBenchmarks {
|
||||
select SOME_COLUMN from SOME_OTHER_TABLE where REPORTING_DATE = :REPORTING_DATE
|
||||
union select SOME_COLUMN from SOME_OTHER_OTHER_TABLE""";
|
||||
|
||||
enhancer = new JSqlParserQueryEnhancer(DeclaredQuery.of(s, true));
|
||||
enhancer = new JSqlParserQueryEnhancer(DeclaredQuery.ofNative(s));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.context.annotation.ComponentScan.Filter;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.data.jpa.repository.query.QueryEnhancerSelector;
|
||||
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
|
||||
import org.springframework.data.repository.config.BootstrapMode;
|
||||
import org.springframework.data.repository.config.DefaultRepositoryBaseClass;
|
||||
@@ -83,46 +84,39 @@ public @interface EnableJpaRepositories {
|
||||
* Returns the postfix to be used when looking up custom repository implementations. Defaults to {@literal Impl}. So
|
||||
* for a repository named {@code PersonRepository} the corresponding implementation class will be looked up scanning
|
||||
* for {@code PersonRepositoryImpl}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String repositoryImplementationPostfix() default "Impl";
|
||||
|
||||
/**
|
||||
* Configures the location of where to find the Spring Data named queries properties file. Will default to
|
||||
* {@code META-INF/jpa-named-queries.properties}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String namedQueriesLocation() default "";
|
||||
|
||||
/**
|
||||
* Returns the key of the {@link QueryLookupStrategy} to be used for lookup queries for query methods. Defaults to
|
||||
* {@link Key#CREATE_IF_NOT_FOUND}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Key queryLookupStrategy() default Key.CREATE_IF_NOT_FOUND;
|
||||
|
||||
/**
|
||||
* Returns the {@link FactoryBean} class to be used for each repository instance. Defaults to
|
||||
* {@link JpaRepositoryFactoryBean}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<?> repositoryFactoryBeanClass() default JpaRepositoryFactoryBean.class;
|
||||
|
||||
/**
|
||||
* Configure the repository base class to be used to create repository proxies for this particular configuration.
|
||||
*
|
||||
* @return
|
||||
* @since 1.9
|
||||
*/
|
||||
Class<?> repositoryBaseClass() default DefaultRepositoryBaseClass.class;
|
||||
|
||||
/**
|
||||
* Configure a specific {@link BeanNameGenerator} to be used when creating the repository beans.
|
||||
* @return the {@link BeanNameGenerator} to be used or the base {@link BeanNameGenerator} interface to indicate context default.
|
||||
*
|
||||
* @return the {@link BeanNameGenerator} to be used or the base {@link BeanNameGenerator} interface to indicate
|
||||
* context default.
|
||||
* @since 3.4
|
||||
*/
|
||||
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
|
||||
@@ -132,22 +126,18 @@ public @interface EnableJpaRepositories {
|
||||
/**
|
||||
* Configures the name of the {@link EntityManagerFactory} bean definition to be used to create repositories
|
||||
* discovered through this annotation. Defaults to {@code entityManagerFactory}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String entityManagerFactoryRef() default "entityManagerFactory";
|
||||
|
||||
/**
|
||||
* Configures the name of the {@link PlatformTransactionManager} bean definition to be used to create repositories
|
||||
* discovered through this annotation. Defaults to {@code transactionManager}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String transactionManagerRef() default "transactionManager";
|
||||
|
||||
/**
|
||||
* Configures whether nested repository-interfaces (e.g. defined as inner classes) should be discovered by the
|
||||
* repositories infrastructure.
|
||||
* repository infrastructure.
|
||||
*/
|
||||
boolean considerNestedRepositories() default false;
|
||||
|
||||
@@ -169,7 +159,6 @@ public @interface EnableJpaRepositories {
|
||||
* completed its bootstrap. {@link BootstrapMode#DEFERRED} is fundamentally the same as {@link BootstrapMode#LAZY},
|
||||
* but triggers repository initialization when the application context finishes its bootstrap.
|
||||
*
|
||||
* @return
|
||||
* @since 2.1
|
||||
*/
|
||||
BootstrapMode bootstrapMode() default BootstrapMode.DEFAULT;
|
||||
@@ -181,4 +170,12 @@ public @interface EnableJpaRepositories {
|
||||
* @return a single character used for escaping.
|
||||
*/
|
||||
char escapeCharacter() default '\\';
|
||||
|
||||
/**
|
||||
* Configures the {@link QueryEnhancerSelector} to select a query enhancer for query introspection and transformation.
|
||||
*
|
||||
* @return a {@link QueryEnhancerSelector} class providing a no-args constructor.
|
||||
* @since 4.0
|
||||
*/
|
||||
Class<? extends QueryEnhancerSelector> queryEnhancerSelector() default QueryEnhancerSelector.DefaultQueryEnhancerSelector.class;
|
||||
}
|
||||
|
||||
@@ -122,6 +122,11 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi
|
||||
}
|
||||
builder.addPropertyValue(ESCAPE_CHARACTER_PROPERTY, getEscapeCharacter(source).orElse('\\'));
|
||||
builder.addPropertyReference("mappingContext", JPA_MAPPING_CONTEXT_BEAN_NAME);
|
||||
|
||||
if (source instanceof AnnotationRepositoryConfigurationSource) {
|
||||
builder.addPropertyValue("queryEnhancerSelector",
|
||||
source.getAttribute("queryEnhancerSelector", Class.class).orElse(null));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -56,7 +56,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
|
||||
private final StringQuery query;
|
||||
private final Map<Class<?>, Boolean> knownProjections = new ConcurrentHashMap<>();
|
||||
private final Lazy<DeclaredQuery> countQuery;
|
||||
private final Lazy<IntrospectedQuery> countQuery;
|
||||
private final ValueExpressionDelegate valueExpressionDelegate;
|
||||
private final QueryRewriter queryRewriter;
|
||||
private final QuerySortRewriter querySortRewriter;
|
||||
@@ -71,37 +71,32 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
* @param em must not be {@literal null}.
|
||||
* @param queryString must not be {@literal null}.
|
||||
* @param countQueryString must not be {@literal null}.
|
||||
* @param queryRewriter must not be {@literal null}.
|
||||
* @param valueExpressionDelegate must not be {@literal null}.
|
||||
* @param queryConfiguration must not be {@literal null}.
|
||||
*/
|
||||
public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,
|
||||
@Nullable String countQueryString, QueryRewriter queryRewriter, ValueExpressionDelegate valueExpressionDelegate) {
|
||||
@Nullable String countQueryString, JpaQueryConfiguration queryConfiguration) {
|
||||
|
||||
super(method, em);
|
||||
|
||||
Assert.hasText(queryString, "Query string must not be null or empty");
|
||||
Assert.notNull(valueExpressionDelegate, "ValueExpressionDelegate must not be null");
|
||||
Assert.notNull(queryRewriter, "QueryRewriter must not be null");
|
||||
Assert.notNull(queryConfiguration, "JpaQueryConfiguration must not be null");
|
||||
|
||||
this.valueExpressionDelegate = valueExpressionDelegate;
|
||||
this.valueExpressionDelegate = queryConfiguration.getValueExpressionDelegate();
|
||||
this.valueExpressionContextProvider = valueExpressionDelegate.createValueContextProvider(method.getParameters());
|
||||
this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), valueExpressionDelegate,
|
||||
method.isNativeQuery());
|
||||
this.query = ExpressionBasedStringQuery.create(queryString, method, queryConfiguration);
|
||||
|
||||
this.countQuery = Lazy.of(() -> {
|
||||
|
||||
if (StringUtils.hasText(countQueryString)) {
|
||||
|
||||
return new ExpressionBasedStringQuery(countQueryString, method.getEntityInformation(), valueExpressionDelegate,
|
||||
method.isNativeQuery());
|
||||
return ExpressionBasedStringQuery.create(countQueryString, method, queryConfiguration);
|
||||
}
|
||||
|
||||
return query.deriveCountQuery(method.getCountQueryProjection());
|
||||
return this.query.deriveCountQuery(method.getCountQueryProjection());
|
||||
});
|
||||
|
||||
this.countParameterBinder = Lazy.of(() -> this.createBinder(this.countQuery.get()));
|
||||
|
||||
this.queryRewriter = queryRewriter;
|
||||
this.queryRewriter = queryConfiguration.getQueryRewriter(method);
|
||||
|
||||
JpaParameters parameters = method.getParameters();
|
||||
|
||||
@@ -115,7 +110,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
}
|
||||
}
|
||||
|
||||
Assert.isTrue(method.isNativeQuery() || !query.usesJdbcStyleParameters(),
|
||||
Assert.isTrue(method.isNativeQuery() || !this.query.usesJdbcStyleParameters(),
|
||||
"JDBC style parameters (?) are not supported for JPA queries");
|
||||
}
|
||||
|
||||
@@ -217,7 +212,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
return createBinder(query);
|
||||
}
|
||||
|
||||
protected ParameterBinder createBinder(DeclaredQuery query) {
|
||||
protected ParameterBinder createBinder(IntrospectedQuery query) {
|
||||
return ParameterBinderFactory.createQueryAwareBinder(getQueryMethod().getParameters(), query,
|
||||
valueExpressionDelegate, valueExpressionContextProvider);
|
||||
}
|
||||
@@ -243,14 +238,14 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
/**
|
||||
* @return the query
|
||||
*/
|
||||
public DeclaredQuery getQuery() {
|
||||
public EntityQuery getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the countQuery
|
||||
*/
|
||||
public DeclaredQuery getCountQuery() {
|
||||
public IntrospectedQuery getCountQuery() {
|
||||
return countQuery.get();
|
||||
}
|
||||
|
||||
@@ -292,8 +287,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
}
|
||||
|
||||
String applySorting(CachableQuery cachableQuery) {
|
||||
|
||||
return QueryEnhancerFactory.forQuery(cachableQuery.getDeclaredQuery())
|
||||
return cachableQuery.getDeclaredQuery().getQueryEnhancer()
|
||||
.rewrite(new DefaultQueryRewriteInformation(cachableQuery.getSort(), cachableQuery.getReturnedType()));
|
||||
}
|
||||
|
||||
@@ -301,7 +295,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
* Query Sort Rewriter interface.
|
||||
*/
|
||||
interface QuerySortRewriter {
|
||||
String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedType);
|
||||
String getSorted(StringQuery query, Sort sort, ReturnedType returnedType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -311,9 +305,8 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedType) {
|
||||
|
||||
return QueryEnhancerFactory.forQuery(query).rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
|
||||
public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType) {
|
||||
return query.getQueryEnhancer().rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,7 +314,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
|
||||
private volatile @Nullable String cachedQueryString;
|
||||
|
||||
public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedType) {
|
||||
public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType) {
|
||||
|
||||
if (sort.isSorted()) {
|
||||
throw new UnsupportedOperationException("NoOpQueryCache does not support sorting");
|
||||
@@ -329,7 +322,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
|
||||
String cachedQueryString = this.cachedQueryString;
|
||||
if (cachedQueryString == null) {
|
||||
this.cachedQueryString = cachedQueryString = QueryEnhancerFactory.forQuery(query)
|
||||
this.cachedQueryString = cachedQueryString = query.getQueryEnhancer()
|
||||
.rewrite(new DefaultQueryRewriteInformation(sort, returnedType));
|
||||
}
|
||||
|
||||
@@ -348,7 +341,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
private volatile @Nullable String cachedQueryString;
|
||||
|
||||
@Override
|
||||
public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedType) {
|
||||
public String getSorted(StringQuery query, Sort sort, ReturnedType returnedType) {
|
||||
|
||||
if (sort.isUnsorted()) {
|
||||
|
||||
@@ -373,21 +366,21 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
|
||||
*/
|
||||
static class CachableQuery {
|
||||
|
||||
private final DeclaredQuery declaredQuery;
|
||||
private final StringQuery query;
|
||||
private final String queryString;
|
||||
private final Sort sort;
|
||||
private final ReturnedType returnedType;
|
||||
|
||||
CachableQuery(DeclaredQuery query, Sort sort, ReturnedType returnedType) {
|
||||
CachableQuery(StringQuery query, Sort sort, ReturnedType returnedType) {
|
||||
|
||||
this.declaredQuery = query;
|
||||
this.query = query;
|
||||
this.queryString = query.getQueryString();
|
||||
this.sort = sort;
|
||||
this.returnedType = returnedType;
|
||||
}
|
||||
|
||||
DeclaredQuery getDeclaredQuery() {
|
||||
return declaredQuery;
|
||||
StringQuery getDeclaredQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
Sort getSort() {
|
||||
|
||||
@@ -15,100 +15,45 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A wrapper for a String representation of a query offering information about the query.
|
||||
* Interface defining the contract to represent a declared query.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Diego Krupitza
|
||||
* @author Mark Paluch
|
||||
* @since 2.0.3
|
||||
*/
|
||||
interface DeclaredQuery {
|
||||
public interface DeclaredQuery {
|
||||
|
||||
/**
|
||||
* Creates a {@literal DeclaredQuery} from a query {@literal String}.
|
||||
* Creates a DeclaredQuery for a JPQL query.
|
||||
*
|
||||
* @param query might be {@literal null} or empty.
|
||||
* @param nativeQuery is a given query is native or not
|
||||
* @return a {@literal DeclaredQuery} instance even for a {@literal null} or empty argument.
|
||||
* @param query the JPQL query string.
|
||||
* @return
|
||||
*/
|
||||
static DeclaredQuery of(@Nullable String query, boolean nativeQuery) {
|
||||
return ObjectUtils.isEmpty(query) ? EmptyDeclaredQuery.EMPTY_QUERY : new StringQuery(query, nativeQuery);
|
||||
static DeclaredQuery ofJpql(String query) {
|
||||
return new DefaultDeclaredQuery(query, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the underlying query has at least one named parameter.
|
||||
* Creates a DeclaredQuery for a native query.
|
||||
*
|
||||
* @param query the native query string.
|
||||
* @return
|
||||
*/
|
||||
boolean hasNamedParameter();
|
||||
static DeclaredQuery ofNative(String query) {
|
||||
return new DefaultDeclaredQuery(query, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the query string.
|
||||
*/
|
||||
String getQueryString();
|
||||
|
||||
/**
|
||||
* Returns the main alias used in the query.
|
||||
*
|
||||
* @return the alias
|
||||
*/
|
||||
@Nullable
|
||||
String getAlias();
|
||||
|
||||
/**
|
||||
* Returns whether the query is using a constructor expression.
|
||||
*
|
||||
* @since 1.10
|
||||
*/
|
||||
boolean hasConstructorExpression();
|
||||
|
||||
/**
|
||||
* Returns whether the query uses the default projection, i.e. returns the main alias defined for the query.
|
||||
*/
|
||||
boolean isDefaultProjection();
|
||||
|
||||
/**
|
||||
* Returns the {@link ParameterBinding}s registered.
|
||||
*/
|
||||
List<ParameterBinding> getParameterBindings();
|
||||
|
||||
/**
|
||||
* Creates a new {@literal DeclaredQuery} representing a count query, i.e. a query returning the number of rows to be
|
||||
* expected from the original query, either derived from the query wrapped by this instance or from the information
|
||||
* passed as arguments.
|
||||
*
|
||||
* @param countQueryProjection an optional return type for the query.
|
||||
* @return a new {@literal DeclaredQuery} instance.
|
||||
*/
|
||||
DeclaredQuery deriveCountQuery(@Nullable String countQueryProjection);
|
||||
|
||||
/**
|
||||
* @return whether paging is implemented in the query itself, e.g. using SpEL expressions.
|
||||
* @since 2.0.6
|
||||
*/
|
||||
default boolean usesPaging() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the query uses JDBC style parameters, i.e. parameters denoted by a simple ? without any index or
|
||||
* name.
|
||||
*
|
||||
* @return Whether the query uses JDBC style parameters.
|
||||
* @since 2.0.6
|
||||
*/
|
||||
boolean usesJdbcStyleParameters();
|
||||
|
||||
/**
|
||||
* Return whether the query is a native query of not.
|
||||
*
|
||||
* @return <code>true</code> if native query otherwise <code>false</code>
|
||||
*/
|
||||
default boolean isNativeQuery() {
|
||||
return false;
|
||||
}
|
||||
boolean isNativeQuery();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2024 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.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class DefaultDeclaredQuery implements DeclaredQuery {
|
||||
|
||||
private final String query;
|
||||
private final boolean nativeQuery;
|
||||
|
||||
DefaultDeclaredQuery(String query, boolean nativeQuery) {
|
||||
this.query = query;
|
||||
this.nativeQuery = nativeQuery;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryString() {
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNativeQuery() {
|
||||
return nativeQuery;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
}
|
||||
if (!(object instanceof DefaultDeclaredQuery that)) {
|
||||
return false;
|
||||
}
|
||||
if (nativeQuery != that.nativeQuery) {
|
||||
return false;
|
||||
}
|
||||
return ObjectUtils.nullSafeEquals(query, that.query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = ObjectUtils.nullSafeHashCode(query);
|
||||
result = 31 * result + (nativeQuery ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (isNativeQuery() ? "[native] " : "[JPQL] ") + getQueryString();
|
||||
}
|
||||
}
|
||||
@@ -18,20 +18,21 @@ package org.springframework.data.jpa.repository.query;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* NULL-Object pattern implementation for {@link DeclaredQuery}.
|
||||
* NULL-Object pattern implementation for {@link IntrospectedQuery}.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @since 2.0.3
|
||||
*/
|
||||
class EmptyDeclaredQuery implements DeclaredQuery {
|
||||
class EmptyIntrospectedQuery implements EntityQuery {
|
||||
|
||||
/**
|
||||
* An implementation implementing the NULL-Object pattern for situations where there is no query.
|
||||
*/
|
||||
static final DeclaredQuery EMPTY_QUERY = new EmptyDeclaredQuery();
|
||||
static final EntityQuery EMPTY_QUERY = new EmptyIntrospectedQuery();
|
||||
|
||||
@Override
|
||||
public boolean hasNamedParameter() {
|
||||
@@ -43,11 +44,15 @@ class EmptyDeclaredQuery implements DeclaredQuery {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getAlias() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNativeQuery() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasConstructorExpression() {
|
||||
return false;
|
||||
@@ -64,10 +69,15 @@ class EmptyDeclaredQuery implements DeclaredQuery {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeclaredQuery deriveCountQuery(@Nullable String countQueryProjection) {
|
||||
public IntrospectedQuery deriveCountQuery(@Nullable String countQueryProjection) {
|
||||
return EMPTY_QUERY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String applySorting(Sort sort) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean usesJdbcStyleParameters() {
|
||||
return false;
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2018-2024 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.springframework.data.domain.Sort;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* A wrapper for a String representation of a query offering information about the query.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Diego Krupitza
|
||||
* @since 2.0.3
|
||||
*/
|
||||
interface EntityQuery extends IntrospectedQuery {
|
||||
|
||||
/**
|
||||
* Creates a DeclaredQuery for a JPQL query.
|
||||
*
|
||||
* @param query the JPQL query string.
|
||||
* @return
|
||||
*/
|
||||
static EntityQuery introspectJpql(String query, QueryEnhancerFactory queryEnhancer) {
|
||||
return ObjectUtils.isEmpty(query) ? EmptyIntrospectedQuery.EMPTY_QUERY
|
||||
: new StringQuery(query, false, queryEnhancer, parameterBindings -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DeclaredQuery for a JPQL query.
|
||||
*
|
||||
* @param query the JPQL query string.
|
||||
* @return
|
||||
*/
|
||||
static EntityQuery introspectJpql(String query, QueryEnhancerSelector selector) {
|
||||
return ObjectUtils.isEmpty(query) ? EmptyIntrospectedQuery.EMPTY_QUERY
|
||||
: new StringQuery(query, false, selector, parameterBindings -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DeclaredQuery for a native query.
|
||||
*
|
||||
* @param query the native query string.
|
||||
* @return
|
||||
*/
|
||||
static EntityQuery introspectNativeQuery(String query, QueryEnhancerFactory queryEnhancer) {
|
||||
return ObjectUtils.isEmpty(query) ? EmptyIntrospectedQuery.EMPTY_QUERY
|
||||
: new StringQuery(query, true, queryEnhancer, parameterBindings -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a DeclaredQuery for a native query.
|
||||
*
|
||||
* @param query the native query string.
|
||||
* @return
|
||||
*/
|
||||
static EntityQuery introspectNativeQuery(String query, QueryEnhancerSelector selector) {
|
||||
return ObjectUtils.isEmpty(query) ? EmptyIntrospectedQuery.EMPTY_QUERY
|
||||
: new StringQuery(query, true, selector, parameterBindings -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the query is using a constructor expression.
|
||||
*
|
||||
* @since 1.10
|
||||
*/
|
||||
boolean hasConstructorExpression();
|
||||
|
||||
/**
|
||||
* Returns whether the query uses the default projection, i.e. returns the main alias defined for the query.
|
||||
*/
|
||||
boolean isDefaultProjection();
|
||||
|
||||
/**
|
||||
* Creates a new {@literal IntrospectedQuery} representing a count query, i.e. a query returning the number of rows to
|
||||
* be expected from the original query, either derived from the query wrapped by this instance or from the information
|
||||
* passed as arguments.
|
||||
*
|
||||
* @param countQueryProjection an optional return type for the query.
|
||||
* @return a new {@literal IntrospectedQuery} instance.
|
||||
*/
|
||||
IntrospectedQuery deriveCountQuery(@Nullable String countQueryProjection);
|
||||
|
||||
String applySorting(Sort sort);
|
||||
|
||||
/**
|
||||
* @return whether paging is implemented in the query itself, e.g. using SpEL expressions.
|
||||
* @since 2.0.6
|
||||
*/
|
||||
default boolean usesPaging() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,7 +30,7 @@ import org.springframework.util.Assert;
|
||||
/**
|
||||
* Extension of {@link StringQuery} that evaluates the given query string as a SpEL template-expression.
|
||||
* <p>
|
||||
* Currently the following template variables are available:
|
||||
* Currently, the following template variables are available:
|
||||
* <ol>
|
||||
* <li>{@code #entityName} - the simple class name of the given entity</li>
|
||||
* <ol>
|
||||
@@ -66,25 +66,13 @@ class ExpressionBasedStringQuery extends StringQuery {
|
||||
* @param query must not be {@literal null} or empty.
|
||||
* @param metadata must not be {@literal null}.
|
||||
* @param parser must not be {@literal null}.
|
||||
* @param nativeQuery is a given query is native or not
|
||||
* @param nativeQuery is a given query is native or not.
|
||||
* @param selector must not be {@literal null}.
|
||||
*/
|
||||
public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, ValueExpressionParser parser,
|
||||
boolean nativeQuery) {
|
||||
super(renderQueryIfExpressionOrReturnQuery(query, metadata, parser), nativeQuery && !containsExpression(query));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link ExpressionBasedStringQuery} from a given {@link DeclaredQuery}.
|
||||
*
|
||||
* @param query the original query. Must not be {@literal null}.
|
||||
* @param metadata the {@link JpaEntityMetadata} for the given entity. Must not be {@literal null}.
|
||||
* @param parser Parser for resolving SpEL expressions. Must not be {@literal null}.
|
||||
* @param nativeQuery is a given query native or not
|
||||
* @return A query supporting SpEL expressions.
|
||||
*/
|
||||
static ExpressionBasedStringQuery from(DeclaredQuery query, JpaEntityMetadata<?> metadata,
|
||||
ValueExpressionParser parser, boolean nativeQuery) {
|
||||
return new ExpressionBasedStringQuery(query.getQueryString(), metadata, parser, nativeQuery);
|
||||
ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, ValueExpressionParser parser,
|
||||
boolean nativeQuery, QueryEnhancerSelector selector) {
|
||||
super(renderQueryIfExpressionOrReturnQuery(query, metadata, parser), nativeQuery && !containsExpression(query),
|
||||
selector, parameterBindings -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,4 +119,11 @@ class ExpressionBasedStringQuery extends StringQuery {
|
||||
private static boolean containsExpression(String query) {
|
||||
return query.contains(ENTITY_NAME_VARIABLE_EXPRESSION);
|
||||
}
|
||||
|
||||
public static StringQuery create(String query, JpaQueryMethod method, JpaQueryConfiguration queryContext) {
|
||||
return new ExpressionBasedStringQuery(query, method.getEntityInformation(),
|
||||
queryContext.getValueExpressionDelegate().getValueExpressionParser(),
|
||||
method.isNativeQuery(), queryContext.getSelector());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2018-2024 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.List;
|
||||
|
||||
/**
|
||||
* A wrapper for a String representation of a query offering information about the query.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Diego Krupitza
|
||||
* @since 2.0.3
|
||||
*/
|
||||
interface IntrospectedQuery extends DeclaredQuery {
|
||||
|
||||
/**
|
||||
* @return whether the underlying query has at least one named parameter.
|
||||
*/
|
||||
boolean hasNamedParameter();
|
||||
|
||||
/**
|
||||
* Returns whether the query uses the default projection, i.e. returns the main alias defined for the query.
|
||||
*/
|
||||
boolean isDefaultProjection();
|
||||
|
||||
/**
|
||||
* Returns the {@link ParameterBinding}s registered.
|
||||
*/
|
||||
List<ParameterBinding> getParameterBindings();
|
||||
|
||||
/**
|
||||
* Returns whether the query uses JDBC style parameters, i.e. parameters denoted by a simple ? without any index or
|
||||
* name.
|
||||
*
|
||||
* @return Whether the query uses JDBC style parameters.
|
||||
* @since 2.0.6
|
||||
*/
|
||||
boolean usesJdbcStyleParameters();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2024 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.springframework.data.jpa.repository.QueryRewriter;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
|
||||
/**
|
||||
* Configuration object holding configuration information for JPA queries within a repository.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class JpaQueryConfiguration {
|
||||
|
||||
private final QueryRewriterProvider queryRewriter;
|
||||
private final QueryEnhancerSelector selector;
|
||||
private final EscapeCharacter escapeCharacter;
|
||||
private final ValueExpressionDelegate valueExpressionDelegate;
|
||||
|
||||
public JpaQueryConfiguration(QueryRewriterProvider queryRewriter, QueryEnhancerSelector selector,
|
||||
ValueExpressionDelegate valueExpressionDelegate, EscapeCharacter escapeCharacter) {
|
||||
|
||||
this.queryRewriter = queryRewriter;
|
||||
this.selector = selector;
|
||||
this.escapeCharacter = escapeCharacter;
|
||||
this.valueExpressionDelegate = valueExpressionDelegate;
|
||||
}
|
||||
|
||||
public QueryRewriter getQueryRewriter(JpaQueryMethod queryMethod) {
|
||||
return queryRewriter.getQueryRewriter(queryMethod);
|
||||
}
|
||||
|
||||
public QueryEnhancerSelector getSelector() {
|
||||
return selector;
|
||||
}
|
||||
|
||||
public EscapeCharacter getEscapeCharacter() {
|
||||
return escapeCharacter;
|
||||
}
|
||||
|
||||
public ValueExpressionDelegate getValueExpressionDelegate() {
|
||||
return valueExpressionDelegate;
|
||||
}
|
||||
}
|
||||
@@ -142,43 +142,34 @@ class JpaQueryEnhancer<Q extends QueryInformation> implements QueryEnhancer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a {@link JpaQueryEnhancer} for {@link DeclaredQuery} using JPQL grammar.
|
||||
* Factory method to create a {@link JpaQueryEnhancer} for {@link IntrospectedQuery} using JPQL grammar.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @return a new {@link JpaQueryEnhancer} using JPQL.
|
||||
*/
|
||||
public static JpaQueryEnhancer<QueryInformation> forJpql(DeclaredQuery query) {
|
||||
|
||||
Assert.notNull(query, "DeclaredQuery must not be null!");
|
||||
|
||||
return JpqlQueryParser.parseQuery(query.getQueryString());
|
||||
public static JpaQueryEnhancer<QueryInformation> forJpql(String query) {
|
||||
return JpqlQueryParser.parseQuery(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a {@link JpaQueryEnhancer} for {@link DeclaredQuery} using HQL grammar.
|
||||
* Factory method to create a {@link JpaQueryEnhancer} for {@link IntrospectedQuery} using HQL grammar.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @return a new {@link JpaQueryEnhancer} using HQL.
|
||||
*/
|
||||
public static JpaQueryEnhancer<HibernateQueryInformation> forHql(DeclaredQuery query) {
|
||||
|
||||
Assert.notNull(query, "DeclaredQuery must not be null!");
|
||||
|
||||
return HqlQueryParser.parseQuery(query.getQueryString());
|
||||
public static JpaQueryEnhancer<HibernateQueryInformation> forHql(String query) {
|
||||
return HqlQueryParser.parseQuery(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a {@link JpaQueryEnhancer} for {@link DeclaredQuery} using EQL grammar.
|
||||
* Factory method to create a {@link JpaQueryEnhancer} for {@link IntrospectedQuery} using EQL grammar.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @return a new {@link JpaQueryEnhancer} using EQL.
|
||||
* @since 3.2
|
||||
*/
|
||||
public static JpaQueryEnhancer<QueryInformation> forEql(DeclaredQuery query) {
|
||||
|
||||
Assert.notNull(query, "DeclaredQuery must not be null!");
|
||||
|
||||
return EqlQueryParser.parseQuery(query.getQueryString());
|
||||
public static JpaQueryEnhancer<QueryInformation> forEql(String query) {
|
||||
return EqlQueryParser.parseQuery(query);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2025 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 jakarta.persistence.EntityManager;
|
||||
|
||||
import org.springframework.data.jpa.repository.QueryRewriter;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.data.repository.query.QueryCreationException;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
|
||||
/**
|
||||
* Factory to create the appropriate {@link RepositoryQuery} for a {@link JpaQueryMethod}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
enum JpaQueryFactory {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
/**
|
||||
* Creates a {@link RepositoryQuery} from the given {@link String} query.
|
||||
*/
|
||||
AbstractJpaQuery fromMethodWithQueryString(JpaQueryMethod method, EntityManager em, String queryString,
|
||||
@Nullable String countQueryString, QueryRewriter queryRewriter,
|
||||
ValueExpressionDelegate valueExpressionDelegate) {
|
||||
|
||||
if (method.isScrollQuery()) {
|
||||
throw QueryCreationException.create(method, "Scroll queries are not supported using String-based queries");
|
||||
}
|
||||
|
||||
return method.isNativeQuery()
|
||||
? new NativeJpaQuery(method, em, queryString, countQueryString, queryRewriter, valueExpressionDelegate)
|
||||
: new SimpleJpaQuery(method, em, queryString, countQueryString, queryRewriter, valueExpressionDelegate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link StoredProcedureJpaQuery} from the given {@link JpaQueryMethod} query.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param em must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public StoredProcedureJpaQuery fromProcedureAnnotation(JpaQueryMethod method, EntityManager em) {
|
||||
|
||||
if (method.isScrollQuery()) {
|
||||
throw QueryCreationException.create(method, "Scroll queries are not supported using stored procedures");
|
||||
}
|
||||
|
||||
return new StoredProcedureJpaQuery(method, em);
|
||||
}
|
||||
}
|
||||
@@ -24,10 +24,10 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.jpa.repository.QueryRewriter;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.query.QueryCreationException;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
@@ -70,33 +70,31 @@ public final class JpaQueryLookupStrategy {
|
||||
|
||||
private final EntityManager em;
|
||||
private final JpaQueryMethodFactory queryMethodFactory;
|
||||
private final QueryRewriterProvider queryRewriterProvider;
|
||||
private final JpaQueryConfiguration configuration;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AbstractQueryLookupStrategy}.
|
||||
*
|
||||
* @param em must not be {@literal null}.
|
||||
* @param queryMethodFactory must not be {@literal null}.
|
||||
* @param configuration must not be {@literal null}.
|
||||
*/
|
||||
public AbstractQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
|
||||
QueryRewriterProvider queryRewriterProvider) {
|
||||
|
||||
Assert.notNull(em, "EntityManager must not be null");
|
||||
Assert.notNull(queryMethodFactory, "JpaQueryMethodFactory must not be null");
|
||||
JpaQueryConfiguration configuration) {
|
||||
|
||||
this.em = em;
|
||||
this.queryMethodFactory = queryMethodFactory;
|
||||
this.queryRewriterProvider = queryRewriterProvider;
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
|
||||
NamedQueries namedQueries) {
|
||||
JpaQueryMethod queryMethod = queryMethodFactory.build(method, metadata, factory);
|
||||
return resolveQuery(queryMethod, queryRewriterProvider.getQueryRewriter(queryMethod), em, namedQueries);
|
||||
return resolveQuery(queryMethod, configuration, em, namedQueries);
|
||||
}
|
||||
|
||||
protected abstract RepositoryQuery resolveQuery(JpaQueryMethod method, QueryRewriter queryRewriter,
|
||||
protected abstract RepositoryQuery resolveQuery(JpaQueryMethod method, JpaQueryConfiguration configuration,
|
||||
EntityManager em, NamedQueries namedQueries);
|
||||
|
||||
}
|
||||
@@ -109,20 +107,16 @@ public final class JpaQueryLookupStrategy {
|
||||
*/
|
||||
private static class CreateQueryLookupStrategy extends AbstractQueryLookupStrategy {
|
||||
|
||||
private final EscapeCharacter escape;
|
||||
|
||||
public CreateQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
|
||||
QueryRewriterProvider queryRewriterProvider, EscapeCharacter escape) {
|
||||
JpaQueryConfiguration configuration) {
|
||||
|
||||
super(em, queryMethodFactory, queryRewriterProvider);
|
||||
|
||||
this.escape = escape;
|
||||
super(em, queryMethodFactory, configuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RepositoryQuery resolveQuery(JpaQueryMethod method, QueryRewriter queryRewriter, EntityManager em,
|
||||
protected RepositoryQuery resolveQuery(JpaQueryMethod method, JpaQueryConfiguration configuration, EntityManager em,
|
||||
NamedQueries namedQueries) {
|
||||
return new PartTreeJpaQuery(method, em, escape);
|
||||
return new PartTreeJpaQuery(method, em, configuration.getEscapeCharacter());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,32 +128,27 @@ public final class JpaQueryLookupStrategy {
|
||||
* @author Thomas Darimont
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
private static class DeclaredQueryLookupStrategy extends AbstractQueryLookupStrategy {
|
||||
|
||||
private final ValueExpressionDelegate valueExpressionDelegate;
|
||||
static class DeclaredQueryLookupStrategy extends AbstractQueryLookupStrategy {
|
||||
|
||||
/**
|
||||
* Creates a new {@link DeclaredQueryLookupStrategy}.
|
||||
*
|
||||
* @param em must not be {@literal null}.
|
||||
* @param queryMethodFactory must not be {@literal null}.
|
||||
* @param delegate must not be {@literal null}.
|
||||
* @param queryRewriterProvider must not be {@literal null}.
|
||||
* @param configuration must not be {@literal null}.
|
||||
*/
|
||||
public DeclaredQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
|
||||
ValueExpressionDelegate delegate, QueryRewriterProvider queryRewriterProvider) {
|
||||
JpaQueryConfiguration configuration) {
|
||||
|
||||
super(em, queryMethodFactory, queryRewriterProvider);
|
||||
|
||||
this.valueExpressionDelegate = delegate;
|
||||
super(em, queryMethodFactory, configuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RepositoryQuery resolveQuery(JpaQueryMethod method, QueryRewriter queryRewriter, EntityManager em,
|
||||
protected RepositoryQuery resolveQuery(JpaQueryMethod method, JpaQueryConfiguration configuration, EntityManager em,
|
||||
NamedQueries namedQueries) {
|
||||
|
||||
if (method.isProcedureQuery()) {
|
||||
return JpaQueryFactory.INSTANCE.fromProcedureAnnotation(method, em);
|
||||
return createProcedureQuery(method, em);
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(method.getAnnotatedQuery())) {
|
||||
@@ -169,17 +158,17 @@ public final class JpaQueryLookupStrategy {
|
||||
"Query method %s is annotated with both, a query and a query name; Using the declared query", method));
|
||||
}
|
||||
|
||||
return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, method.getRequiredAnnotatedQuery(),
|
||||
getCountQuery(method, namedQueries, em), queryRewriter, valueExpressionDelegate);
|
||||
return createStringQuery(method, em, method.getRequiredAnnotatedQuery(),
|
||||
getCountQuery(method, namedQueries, em), configuration);
|
||||
}
|
||||
|
||||
String name = method.getNamedQueryName();
|
||||
if (namedQueries.hasQuery(name)) {
|
||||
return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, namedQueries.getQuery(name),
|
||||
getCountQuery(method, namedQueries, em), queryRewriter, valueExpressionDelegate);
|
||||
return createStringQuery(method, em, namedQueries.getQuery(name), getCountQuery(method, namedQueries, em),
|
||||
configuration);
|
||||
}
|
||||
|
||||
RepositoryQuery query = NamedQuery.lookupFrom(method, em, queryRewriter);
|
||||
RepositoryQuery query = NamedQuery.lookupFrom(method, em, configuration.getSelector());
|
||||
|
||||
return query != null ? query : NO_QUERY;
|
||||
}
|
||||
@@ -208,6 +197,44 @@ public final class JpaQueryLookupStrategy {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link RepositoryQuery} from the given {@link String} query.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param em must not be {@literal null}.
|
||||
* @param queryString must not be {@literal null}.
|
||||
* @param countQueryString must not be {@literal null}.
|
||||
* @param configuration must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
static AbstractJpaQuery createStringQuery(JpaQueryMethod method, EntityManager em, String queryString,
|
||||
@Nullable String countQueryString, JpaQueryConfiguration configuration) {
|
||||
|
||||
if (method.isScrollQuery()) {
|
||||
throw QueryCreationException.create(method, "Scroll queries are not supported using String-based queries");
|
||||
}
|
||||
|
||||
return method.isNativeQuery() ? new NativeJpaQuery(method, em, queryString, countQueryString, configuration)
|
||||
: new SimpleJpaQuery(method, em, queryString, countQueryString, configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link StoredProcedureJpaQuery} from the given {@link JpaQueryMethod} query.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param em must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
static StoredProcedureJpaQuery createProcedureQuery(JpaQueryMethod method, EntityManager em) {
|
||||
|
||||
if (method.isScrollQuery()) {
|
||||
throw QueryCreationException.create(method, "Scroll queries are not supported using stored procedures");
|
||||
}
|
||||
|
||||
return new StoredProcedureJpaQuery(method, em);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -230,31 +257,29 @@ public final class JpaQueryLookupStrategy {
|
||||
* @param queryMethodFactory must not be {@literal null}.
|
||||
* @param createStrategy must not be {@literal null}.
|
||||
* @param lookupStrategy must not be {@literal null}.
|
||||
* @param configuration must not be {@literal null}.
|
||||
*/
|
||||
public CreateIfNotFoundQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
|
||||
CreateQueryLookupStrategy createStrategy, DeclaredQueryLookupStrategy lookupStrategy,
|
||||
QueryRewriterProvider queryRewriterProvider) {
|
||||
JpaQueryConfiguration configuration) {
|
||||
|
||||
super(em, queryMethodFactory, queryRewriterProvider);
|
||||
|
||||
Assert.notNull(createStrategy, "CreateQueryLookupStrategy must not be null");
|
||||
Assert.notNull(lookupStrategy, "DeclaredQueryLookupStrategy must not be null");
|
||||
super(em, queryMethodFactory, configuration);
|
||||
|
||||
this.createStrategy = createStrategy;
|
||||
this.lookupStrategy = lookupStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RepositoryQuery resolveQuery(JpaQueryMethod method, QueryRewriter queryRewriter, EntityManager em,
|
||||
protected RepositoryQuery resolveQuery(JpaQueryMethod method, JpaQueryConfiguration configuration, EntityManager em,
|
||||
NamedQueries namedQueries) {
|
||||
|
||||
RepositoryQuery lookupQuery = lookupStrategy.resolveQuery(method, queryRewriter, em, namedQueries);
|
||||
RepositoryQuery lookupQuery = lookupStrategy.resolveQuery(method, configuration, em, namedQueries);
|
||||
|
||||
if (lookupQuery != NO_QUERY) {
|
||||
return lookupQuery;
|
||||
}
|
||||
|
||||
return createStrategy.resolveQuery(method, queryRewriter, em, namedQueries);
|
||||
return createStrategy.resolveQuery(method, configuration, em, namedQueries);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,25 +289,20 @@ public final class JpaQueryLookupStrategy {
|
||||
* @param em must not be {@literal null}.
|
||||
* @param queryMethodFactory must not be {@literal null}.
|
||||
* @param key may be {@literal null}.
|
||||
* @param delegate must not be {@literal null}.
|
||||
* @param queryRewriterProvider must not be {@literal null}.
|
||||
* @param escape must not be {@literal null}.
|
||||
* @param configuration must not be {@literal null}.
|
||||
*/
|
||||
public static QueryLookupStrategy create(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
|
||||
@Nullable Key key, ValueExpressionDelegate delegate, QueryRewriterProvider queryRewriterProvider,
|
||||
EscapeCharacter escape) {
|
||||
@Nullable Key key, JpaQueryConfiguration configuration) {
|
||||
|
||||
Assert.notNull(em, "EntityManager must not be null");
|
||||
Assert.notNull(delegate, "ValueExpressionDelegate must not be null");
|
||||
Assert.notNull(configuration, "JpaQueryConfiguration must not be null");
|
||||
|
||||
return switch (key != null ? key : Key.CREATE_IF_NOT_FOUND) {
|
||||
case CREATE -> new CreateQueryLookupStrategy(em, queryMethodFactory, queryRewriterProvider, escape);
|
||||
case USE_DECLARED_QUERY ->
|
||||
new DeclaredQueryLookupStrategy(em, queryMethodFactory, delegate, queryRewriterProvider);
|
||||
case CREATE -> new CreateQueryLookupStrategy(em, queryMethodFactory, configuration);
|
||||
case USE_DECLARED_QUERY -> new DeclaredQueryLookupStrategy(em, queryMethodFactory, configuration);
|
||||
case CREATE_IF_NOT_FOUND -> new CreateIfNotFoundQueryLookupStrategy(em, queryMethodFactory,
|
||||
new CreateQueryLookupStrategy(em, queryMethodFactory, queryRewriterProvider, escape),
|
||||
new DeclaredQueryLookupStrategy(em, queryMethodFactory, delegate, queryRewriterProvider),
|
||||
queryRewriterProvider);
|
||||
new CreateQueryLookupStrategy(em, queryMethodFactory, configuration),
|
||||
new DeclaredQueryLookupStrategy(em, queryMethodFactory, configuration), configuration);
|
||||
default -> throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s", key));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -55,12 +55,12 @@ final class NamedQuery extends AbstractJpaQuery {
|
||||
private final String countQueryName;
|
||||
private final @Nullable String countProjection;
|
||||
private final boolean namedCountQueryIsPresent;
|
||||
private final Lazy<DeclaredQuery> declaredQuery;
|
||||
private final Lazy<EntityQuery> entityQuery;
|
||||
|
||||
/**
|
||||
* Creates a new {@link NamedQuery}.
|
||||
*/
|
||||
private NamedQuery(JpaQueryMethod method, EntityManager em, QueryRewriter queryRewriter) {
|
||||
private NamedQuery(JpaQueryMethod method, EntityManager em, QueryEnhancerSelector selector, QueryRewriter queryRewriter) {
|
||||
|
||||
super(method, em);
|
||||
|
||||
@@ -96,8 +96,12 @@ final class NamedQuery extends AbstractJpaQuery {
|
||||
|
||||
String queryString = extractor.extractQueryString(query);
|
||||
|
||||
this.declaredQuery = Lazy
|
||||
.of(() -> DeclaredQuery.of(queryString, method.isNativeQuery() || query.toString().contains("NativeQuery")));
|
||||
// TODO: What is queryString is null?
|
||||
if (method.isNativeQuery() || (query != null && query.toString().contains("NativeQuery"))) {
|
||||
this.entityQuery = Lazy.of(() -> EntityQuery.introspectNativeQuery(queryString, selector));
|
||||
} else {
|
||||
this.entityQuery = Lazy.of(() -> EntityQuery.introspectJpql(queryString, selector));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,10 +134,10 @@ final class NamedQuery extends AbstractJpaQuery {
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param em must not be {@literal null}.
|
||||
* @param queryRewriter must not be {@literal null}.
|
||||
* @param selector must not be {@literal null}.
|
||||
*/
|
||||
public static @Nullable RepositoryQuery lookupFrom(JpaQueryMethod method, EntityManager em,
|
||||
QueryRewriter queryRewriter) {
|
||||
QueryEnhancerSelector selector) {
|
||||
|
||||
String queryName = method.getNamedQueryName();
|
||||
|
||||
@@ -151,7 +155,7 @@ final class NamedQuery extends AbstractJpaQuery {
|
||||
method.isNativeQuery() ? "NativeQuery" : "Query"));
|
||||
}
|
||||
|
||||
RepositoryQuery query = new NamedQuery(method, em, queryRewriter);
|
||||
RepositoryQuery query = new NamedQuery(method, em, selector);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(String.format("Found named query '%s'", queryName));
|
||||
}
|
||||
@@ -188,7 +192,7 @@ final class NamedQuery extends AbstractJpaQuery {
|
||||
|
||||
} else {
|
||||
|
||||
String countQueryString = declaredQuery.get().deriveCountQuery(countProjection).getQueryString();
|
||||
String countQueryString = entityQuery.get().deriveCountQuery(countProjection).getQueryString();
|
||||
countQueryString = potentiallyRewriteQuery(countQueryString, accessor.getSort(), accessor.getPageable());
|
||||
cacheKey = countQueryString;
|
||||
countQuery = em.createQuery(countQueryString, Long.class);
|
||||
@@ -219,7 +223,7 @@ final class NamedQuery extends AbstractJpaQuery {
|
||||
return type.isInterface() ? Tuple.class : null;
|
||||
}
|
||||
|
||||
return declaredQuery.get().hasConstructorExpression() //
|
||||
return entityQuery.get().hasConstructorExpression() //
|
||||
? null //
|
||||
: super.getTypeToRead(returnedType);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.repository.NativeQuery;
|
||||
import org.springframework.data.jpa.repository.QueryRewriter;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
@@ -43,7 +42,7 @@ import org.springframework.util.ObjectUtils;
|
||||
* @author Mark Paluch
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
final class NativeJpaQuery extends AbstractStringBasedJpaQuery {
|
||||
class NativeJpaQuery extends AbstractStringBasedJpaQuery {
|
||||
|
||||
private final @Nullable String sqlResultSetMapping;
|
||||
|
||||
@@ -56,13 +55,12 @@ final class NativeJpaQuery extends AbstractStringBasedJpaQuery {
|
||||
* @param em must not be {@literal null}.
|
||||
* @param queryString must not be {@literal null} or empty.
|
||||
* @param countQueryString must not be {@literal null} or empty.
|
||||
* @param rewriter the query rewriter to use.
|
||||
* @param valueExpressionDelegate must not be {@literal null}.
|
||||
* @param queryConfiguration must not be {@literal null}.
|
||||
*/
|
||||
public NativeJpaQuery(JpaQueryMethod method, EntityManager em, String queryString, @Nullable String countQueryString,
|
||||
QueryRewriter rewriter, ValueExpressionDelegate valueExpressionDelegate) {
|
||||
JpaQueryConfiguration queryConfiguration) {
|
||||
|
||||
super(method, em, queryString, countQueryString, rewriter, valueExpressionDelegate);
|
||||
super(method, em, queryString, countQueryString, queryConfiguration);
|
||||
|
||||
MergedAnnotations annotations = MergedAnnotations.from(method.getMethod());
|
||||
MergedAnnotation<NativeQuery> annotation = annotations.get(NativeQuery.class);
|
||||
|
||||
@@ -84,7 +84,7 @@ class ParameterBinderFactory {
|
||||
* @return a {@link ParameterBinder} that can assign values for the method parameters to query parameters of a
|
||||
* {@link jakarta.persistence.Query} while processing SpEL expressions where applicable.
|
||||
*/
|
||||
static ParameterBinder createQueryAwareBinder(JpaParameters parameters, DeclaredQuery query,
|
||||
static ParameterBinder createQueryAwareBinder(JpaParameters parameters, IntrospectedQuery query,
|
||||
ValueExpressionParser parser, ValueEvaluationContextProvider evaluationContextProvider) {
|
||||
|
||||
Assert.notNull(parameters, "JpaParameters must not be null");
|
||||
@@ -99,8 +99,10 @@ class ParameterBinderFactory {
|
||||
QueryParameterSetterFactory basicSetterFactory = QueryParameterSetterFactory.basic(parameters,
|
||||
query.hasNamedParameter());
|
||||
|
||||
boolean usesPaging = query instanceof EntityQuery eq && eq.usesPaging();
|
||||
|
||||
return new ParameterBinder(parameters, createSetters(bindings, query, expressionSetterFactory, basicSetterFactory),
|
||||
!query.usesPaging());
|
||||
!usesPaging);
|
||||
}
|
||||
|
||||
static List<ParameterBinding> getBindings(JpaParameters parameters) {
|
||||
@@ -124,26 +126,26 @@ class ParameterBinderFactory {
|
||||
|
||||
private static Iterable<QueryParameterSetter> createSetters(List<ParameterBinding> parameterBindings,
|
||||
QueryParameterSetterFactory... factories) {
|
||||
return createSetters(parameterBindings, EmptyDeclaredQuery.EMPTY_QUERY, factories);
|
||||
return createSetters(parameterBindings, EmptyIntrospectedQuery.EMPTY_QUERY, factories);
|
||||
}
|
||||
|
||||
private static Iterable<QueryParameterSetter> createSetters(List<ParameterBinding> parameterBindings,
|
||||
DeclaredQuery declaredQuery, QueryParameterSetterFactory... strategies) {
|
||||
IntrospectedQuery query, QueryParameterSetterFactory... strategies) {
|
||||
|
||||
List<QueryParameterSetter> setters = new ArrayList<>(parameterBindings.size());
|
||||
for (ParameterBinding parameterBinding : parameterBindings) {
|
||||
setters.add(createQueryParameterSetter(parameterBinding, strategies, declaredQuery));
|
||||
setters.add(createQueryParameterSetter(parameterBinding, strategies, query));
|
||||
}
|
||||
|
||||
return setters;
|
||||
}
|
||||
|
||||
private static QueryParameterSetter createQueryParameterSetter(ParameterBinding binding,
|
||||
QueryParameterSetterFactory[] strategies, DeclaredQuery declaredQuery) {
|
||||
QueryParameterSetterFactory[] strategies, IntrospectedQuery query) {
|
||||
|
||||
for (QueryParameterSetterFactory strategy : strategies) {
|
||||
|
||||
QueryParameterSetter setter = strategy.create(binding);
|
||||
QueryParameterSetter setter = strategy.create(binding, query);
|
||||
|
||||
if (setter != null) {
|
||||
return setter;
|
||||
|
||||
@@ -66,7 +66,6 @@ public interface QueryEnhancer {
|
||||
*
|
||||
* @return non-null {@link DeclaredQuery} that wraps the query.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
DeclaredQuery getQuery();
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2024 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.provider.PersistenceProvider;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Pre-defined QueryEnhancerFactories to be used for query enhancement.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class QueryEnhancerFactories {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(QueryEnhancerFactory.class);
|
||||
|
||||
static final boolean jSqlParserPresent = ClassUtils.isPresent("net.sf.jsqlparser.parser.JSqlParser",
|
||||
QueryEnhancerFactory.class.getClassLoader());
|
||||
|
||||
static {
|
||||
|
||||
if (jSqlParserPresent) {
|
||||
LOG.info("JSqlParser is in classpath; If applicable, JSqlParser will be used");
|
||||
}
|
||||
|
||||
if (PersistenceProvider.ECLIPSELINK.isPresent()) {
|
||||
LOG.info("EclipseLink is in classpath; If applicable, EQL parser will be used.");
|
||||
}
|
||||
|
||||
if (PersistenceProvider.HIBERNATE.isPresent()) {
|
||||
LOG.info("Hibernate is in classpath; If applicable, HQL parser will be used.");
|
||||
}
|
||||
}
|
||||
|
||||
enum BuiltinQueryEnhancerFactories implements QueryEnhancerFactory {
|
||||
|
||||
FALLBACK {
|
||||
@Override
|
||||
public boolean supports(DeclaredQuery query) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryEnhancer create(DeclaredQuery query) {
|
||||
return new DefaultQueryEnhancer(query);
|
||||
}
|
||||
},
|
||||
|
||||
JSQLPARSER {
|
||||
@Override
|
||||
public boolean supports(DeclaredQuery query) {
|
||||
return query.isNativeQuery();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryEnhancer create(DeclaredQuery query) {
|
||||
if (jSqlParserPresent) {
|
||||
return new JSqlParserQueryEnhancer(query);
|
||||
}
|
||||
|
||||
throw new IllegalStateException("JSQLParser is not available on the class path");
|
||||
}
|
||||
},
|
||||
|
||||
HQL {
|
||||
@Override
|
||||
public boolean supports(DeclaredQuery query) {
|
||||
return !query.isNativeQuery();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryEnhancer create(DeclaredQuery query) {
|
||||
return JpaQueryEnhancer.forHql(query.getQueryString());
|
||||
}
|
||||
},
|
||||
EQL {
|
||||
@Override
|
||||
public boolean supports(DeclaredQuery query) {
|
||||
return !query.isNativeQuery();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryEnhancer create(DeclaredQuery query) {
|
||||
return JpaQueryEnhancer.forEql(query.getQueryString());
|
||||
}
|
||||
},
|
||||
JPQL {
|
||||
@Override
|
||||
public boolean supports(DeclaredQuery query) {
|
||||
return !query.isNativeQuery();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryEnhancer create(DeclaredQuery query) {
|
||||
return JpaQueryEnhancer.forJpql(query.getQueryString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default fallback {@link QueryEnhancerFactory} using regex-based detection. This factory supports only
|
||||
* simple SQL queries.
|
||||
*
|
||||
* @return fallback {@link QueryEnhancerFactory} using regex-based detection.
|
||||
*/
|
||||
public static QueryEnhancerFactory fallback() {
|
||||
return BuiltinQueryEnhancerFactories.FALLBACK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link QueryEnhancerFactory} that uses <a href="https://github.com/JSQLParser/JSqlParser">JSqlParser</a>
|
||||
* if it is available from the class path.
|
||||
*
|
||||
* @return a {@link QueryEnhancerFactory} that uses <a href="https://github.com/JSQLParser/JSqlParser">JSqlParser</a>.
|
||||
* @throws IllegalStateException if JSQLParser is not on the class path.
|
||||
*/
|
||||
public static QueryEnhancerFactory jsqlparser() {
|
||||
|
||||
if (!jSqlParserPresent) {
|
||||
throw new IllegalStateException("JSQLParser is not available on the class path");
|
||||
}
|
||||
|
||||
return BuiltinQueryEnhancerFactories.JSQLPARSER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link QueryEnhancerFactory} using HQL (Hibernate Query Language) parser.
|
||||
*
|
||||
* @return a {@link QueryEnhancerFactory} using HQL (Hibernate Query Language) parser.
|
||||
*/
|
||||
public static QueryEnhancerFactory hql() {
|
||||
return BuiltinQueryEnhancerFactories.HQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link QueryEnhancerFactory} using EQL (EclipseLink Query Language) parser.
|
||||
*
|
||||
* @return a {@link QueryEnhancerFactory} using EQL (EclipseLink Query Language) parser.
|
||||
*/
|
||||
public static QueryEnhancerFactory eql() {
|
||||
return BuiltinQueryEnhancerFactories.EQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link QueryEnhancerFactory} using JPQL (Jakarta Persistence Query Language) parser as per the JPA spec.
|
||||
*
|
||||
* @return a {@link QueryEnhancerFactory} using JPQL (Jakarta Persistence Query Language) parser as per the JPA spec.
|
||||
*/
|
||||
public static QueryEnhancerFactory jpql() {
|
||||
return BuiltinQueryEnhancerFactories.JPQL;
|
||||
}
|
||||
}
|
||||
@@ -15,133 +15,41 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.SpringProperties;
|
||||
import org.springframework.data.jpa.provider.PersistenceProvider;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Encapsulates different strategies for the creation of a {@link QueryEnhancer} from a {@link DeclaredQuery}.
|
||||
* Encapsulates different strategies for the creation of a {@link QueryEnhancer} from a {@link IntrospectedQuery}.
|
||||
*
|
||||
* @author Diego Krupitza
|
||||
* @author Greg Turnquist
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.7.0
|
||||
* @since 2.7
|
||||
*/
|
||||
public final class QueryEnhancerFactory {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(QueryEnhancerFactory.class);
|
||||
private static final NativeQueryEnhancer NATIVE_QUERY_ENHANCER;
|
||||
|
||||
static {
|
||||
|
||||
NATIVE_QUERY_ENHANCER = NativeQueryEnhancer.select();
|
||||
|
||||
if (PersistenceProvider.ECLIPSELINK.isPresent()) {
|
||||
LOG.info("EclipseLink is in classpath; If applicable, EQL parser will be used.");
|
||||
}
|
||||
|
||||
if (PersistenceProvider.HIBERNATE.isPresent()) {
|
||||
LOG.info("Hibernate is in classpath; If applicable, HQL parser will be used.");
|
||||
}
|
||||
}
|
||||
|
||||
private QueryEnhancerFactory() {}
|
||||
public interface QueryEnhancerFactory {
|
||||
|
||||
/**
|
||||
* Creates a new {@link QueryEnhancer} for the given {@link DeclaredQuery}.
|
||||
* Returns whether this QueryEnhancerFactory supports the given {@link DeclaredQuery}.
|
||||
*
|
||||
* @param query the query to be enhanced and introspected.
|
||||
* @return {@code true} if this QueryEnhancer supports the given query; {@code false} otherwise.
|
||||
*/
|
||||
boolean supports(DeclaredQuery query);
|
||||
|
||||
/**
|
||||
* Creates a new {@link QueryEnhancer} for the given query.
|
||||
*
|
||||
* @param query the query to be enhanced and introspected.
|
||||
* @return
|
||||
*/
|
||||
QueryEnhancer create(DeclaredQuery query);
|
||||
|
||||
/**
|
||||
* Creates a new {@link QueryEnhancerFactory} for the given {@link DeclaredQuery}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @return an implementation of {@link QueryEnhancer} that suits the query the most
|
||||
*/
|
||||
public static QueryEnhancer forQuery(DeclaredQuery query) {
|
||||
|
||||
if (query.isNativeQuery()) {
|
||||
return getNativeQueryEnhancer(query);
|
||||
}
|
||||
|
||||
if (PersistenceProvider.HIBERNATE.isPresent()) {
|
||||
return JpaQueryEnhancer.forHql(query);
|
||||
} else if (PersistenceProvider.ECLIPSELINK.isPresent()) {
|
||||
return JpaQueryEnhancer.forEql(query);
|
||||
} else {
|
||||
return JpaQueryEnhancer.forJpql(query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the native query enhancer for the given {@link DeclaredQuery query} based on {@link #NATIVE_QUERY_ENHANCER}.
|
||||
*
|
||||
* @param query the declared query.
|
||||
* @return new instance of {@link QueryEnhancer}.
|
||||
*/
|
||||
private static QueryEnhancer getNativeQueryEnhancer(DeclaredQuery query) {
|
||||
|
||||
if (NATIVE_QUERY_ENHANCER.equals(NativeQueryEnhancer.JSQLPARSER)) {
|
||||
return new JSqlParserQueryEnhancer(query);
|
||||
}
|
||||
|
||||
return new DefaultQueryEnhancer(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible choices for the {@link #NATIVE_PARSER_PROPERTY}. Resolve the parser through {@link #select()}.
|
||||
*
|
||||
* @since 3.3.5
|
||||
*/
|
||||
enum NativeQueryEnhancer {
|
||||
|
||||
AUTO, REGEX, JSQLPARSER;
|
||||
|
||||
static final String NATIVE_PARSER_PROPERTY = "spring.data.jpa.query.native.parser";
|
||||
|
||||
static final boolean JSQLPARSER_PRESENT = ClassUtils.isPresent("net.sf.jsqlparser.parser.JSqlParser", null);
|
||||
|
||||
/**
|
||||
* @return the current selection considering classpath availability and user selection via
|
||||
* {@link #NATIVE_PARSER_PROPERTY}.
|
||||
*/
|
||||
static NativeQueryEnhancer select() {
|
||||
|
||||
NativeQueryEnhancer selected = resolve();
|
||||
|
||||
if (selected.equals(NativeQueryEnhancer.JSQLPARSER)) {
|
||||
LOG.info("User choice: Using JSqlParser");
|
||||
return NativeQueryEnhancer.JSQLPARSER;
|
||||
}
|
||||
|
||||
if (selected.equals(NativeQueryEnhancer.REGEX)) {
|
||||
LOG.info("Using Regex QueryEnhancer");
|
||||
return NativeQueryEnhancer.REGEX;
|
||||
}
|
||||
|
||||
if (!JSQLPARSER_PRESENT) {
|
||||
return NativeQueryEnhancer.REGEX;
|
||||
}
|
||||
|
||||
LOG.info("JSqlParser is in classpath; If applicable, JSqlParser will be used.");
|
||||
return NativeQueryEnhancer.JSQLPARSER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve {@link NativeQueryEnhancer} from {@link SpringProperties}.
|
||||
*
|
||||
* @return the {@link NativeQueryEnhancer} constant.
|
||||
*/
|
||||
private static NativeQueryEnhancer resolve() {
|
||||
|
||||
String name = SpringProperties.getProperty(NATIVE_PARSER_PROPERTY);
|
||||
|
||||
if (StringUtils.hasText(name)) {
|
||||
return ObjectUtils.caseInsensitiveValueOf(NativeQueryEnhancer.values(), name);
|
||||
}
|
||||
|
||||
return AUTO;
|
||||
}
|
||||
static QueryEnhancerFactory forQuery(DeclaredQuery query) {
|
||||
return QueryEnhancerSelector.DEFAULT_SELECTOR.select(query);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2024 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.springframework.data.jpa.provider.PersistenceProvider;
|
||||
|
||||
/**
|
||||
* Interface declaring a strategy to select a {@link QueryEnhancer} for a given {@link DeclaredQuery query}.
|
||||
* <p>
|
||||
* Enhancers are selected when introspecting a query to determine their selection, joins, aliases and other information
|
||||
* so that query methods can derive count queries, apply sorting and perform other transformations.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface QueryEnhancerSelector {
|
||||
|
||||
/**
|
||||
* Default selector strategy.
|
||||
*/
|
||||
QueryEnhancerSelector DEFAULT_SELECTOR = new DefaultQueryEnhancerSelector();
|
||||
|
||||
/**
|
||||
* Select a {@link QueryEnhancer} for a {@link DeclaredQuery query}.
|
||||
*
|
||||
* @param query
|
||||
* @return
|
||||
*/
|
||||
QueryEnhancerFactory select(DeclaredQuery query);
|
||||
|
||||
/**
|
||||
* Default {@link QueryEnhancerSelector} implementation using class-path information to determine enhancer
|
||||
* availability. Subclasses may provide a different configuration by using the protected constructor.
|
||||
*/
|
||||
class DefaultQueryEnhancerSelector implements QueryEnhancerSelector {
|
||||
|
||||
protected static QueryEnhancerFactory DEFAULT_NATIVE;
|
||||
protected static QueryEnhancerFactory DEFAULT_JPQL;
|
||||
|
||||
static {
|
||||
|
||||
DEFAULT_NATIVE = QueryEnhancerFactories.jSqlParserPresent ? QueryEnhancerFactories.jsqlparser()
|
||||
: QueryEnhancerFactories.fallback();
|
||||
|
||||
if (PersistenceProvider.HIBERNATE.isPresent()) {
|
||||
DEFAULT_JPQL = QueryEnhancerFactories.hql();
|
||||
} else if (PersistenceProvider.ECLIPSELINK.isPresent()) {
|
||||
DEFAULT_JPQL = QueryEnhancerFactories.eql();
|
||||
} else {
|
||||
DEFAULT_JPQL = QueryEnhancerFactories.jpql();
|
||||
}
|
||||
}
|
||||
|
||||
private final QueryEnhancerFactory nativeQuery;
|
||||
private final QueryEnhancerFactory jpql;
|
||||
|
||||
public DefaultQueryEnhancerSelector() {
|
||||
this(DEFAULT_NATIVE, DEFAULT_JPQL);
|
||||
}
|
||||
|
||||
protected DefaultQueryEnhancerSelector(QueryEnhancerFactory nativeQuery, QueryEnhancerFactory jpql) {
|
||||
this.nativeQuery = nativeQuery;
|
||||
this.jpql = jpql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default JPQL {@link QueryEnhancerFactory} based on class path presence of Hibernate and EclipseLink.
|
||||
*
|
||||
* @return the default JPQL {@link QueryEnhancerFactory}.
|
||||
*/
|
||||
public static QueryEnhancerFactory jpql() {
|
||||
return DEFAULT_JPQL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryEnhancerFactory select(DeclaredQuery query) {
|
||||
return jpql.supports(query) ? jpql : nativeQuery;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ abstract class QueryParameterSetterFactory {
|
||||
* @param binding the parameter binding to create a {@link QueryParameterSetter} for.
|
||||
* @return
|
||||
*/
|
||||
abstract @Nullable QueryParameterSetter create(ParameterBinding binding);
|
||||
abstract @Nullable QueryParameterSetter create(ParameterBinding binding, IntrospectedQuery introspectedQuery);
|
||||
|
||||
/**
|
||||
* Creates a new {@link QueryParameterSetterFactory} for the given {@link JpaParameters}.
|
||||
@@ -116,8 +116,8 @@ abstract class QueryParameterSetterFactory {
|
||||
? parameter.getRequiredTemporalType() //
|
||||
: null;
|
||||
|
||||
return QueryParameterSetter.create(valueExtractor.andThen(binding::prepare),
|
||||
ParameterImpl.of(parameter, binding), temporalType);
|
||||
return QueryParameterSetter.create(valueExtractor.andThen(binding::prepare), ParameterImpl.of(parameter, binding),
|
||||
temporalType);
|
||||
}
|
||||
|
||||
static @Nullable JpaParameter findParameterForBinding(Parameters<JpaParameters, JpaParameter> parameters, String name) {
|
||||
@@ -180,7 +180,7 @@ abstract class QueryParameterSetterFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable QueryParameterSetter create(ParameterBinding binding) {
|
||||
public @Nullable QueryParameterSetter create(ParameterBinding binding, IntrospectedQuery introspectedQuery) {
|
||||
|
||||
if (!(binding.getOrigin() instanceof ParameterBinding.Expression e)) {
|
||||
return null;
|
||||
@@ -212,7 +212,7 @@ abstract class QueryParameterSetterFactory {
|
||||
private static class SyntheticParameterSetterFactory extends QueryParameterSetterFactory {
|
||||
|
||||
@Override
|
||||
public @Nullable QueryParameterSetter create(ParameterBinding binding) {
|
||||
public @Nullable QueryParameterSetter create(ParameterBinding binding, IntrospectedQuery query) {
|
||||
|
||||
if (!(binding.getOrigin() instanceof ParameterBinding.Synthetic s)) {
|
||||
return null;
|
||||
@@ -248,7 +248,7 @@ abstract class QueryParameterSetterFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable QueryParameterSetter create(ParameterBinding binding) {
|
||||
public @Nullable QueryParameterSetter create(ParameterBinding binding, IntrospectedQuery introspectedQuery) {
|
||||
|
||||
Assert.notNull(binding, "Binding must not be null");
|
||||
|
||||
@@ -294,22 +294,7 @@ abstract class QueryParameterSetterFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable QueryParameterSetter create(ParameterBinding binding) {
|
||||
|
||||
if (!binding.getOrigin().isMethodArgument()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int parameterIndex = binding.getRequiredPosition() - 1;
|
||||
|
||||
Assert.isTrue( //
|
||||
parameterIndex < parameters.getNumberOfParameters(), //
|
||||
() -> String.format( //
|
||||
"At least %s parameter(s) provided but only %s parameter(s) present in query", //
|
||||
binding.getRequiredPosition(), //
|
||||
parameters.getNumberOfParameters() //
|
||||
) //
|
||||
);
|
||||
public @Nullable QueryParameterSetter create(ParameterBinding binding, IntrospectedQuery query) {
|
||||
|
||||
if (binding instanceof ParameterBinding.PartTreeParameterBinding ptb) {
|
||||
|
||||
@@ -317,7 +302,7 @@ abstract class QueryParameterSetterFactory {
|
||||
return QueryParameterSetter.NOOP;
|
||||
}
|
||||
|
||||
return super.create(binding);
|
||||
return super.create(binding, query);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -445,7 +445,7 @@ public abstract class QueryUtils {
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @return Might return {@literal null}.
|
||||
* @deprecated use {@link DeclaredQuery#getAlias()} instead.
|
||||
* @deprecated use {@link IntrospectedQuery#getAlias()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static @Nullable String detectAlias(String query) {
|
||||
@@ -554,7 +554,7 @@ public abstract class QueryUtils {
|
||||
*
|
||||
* @param originalQuery must not be {@literal null} or empty.
|
||||
* @return Guaranteed to be not {@literal null}.
|
||||
* @deprecated use {@link DeclaredQuery#deriveCountQuery(String)} instead.
|
||||
* @deprecated use {@link IntrospectedQuery#deriveCountQuery(String)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static String createCountQueryFor(String originalQuery) {
|
||||
@@ -568,7 +568,7 @@ public abstract class QueryUtils {
|
||||
* @param countProjection may be {@literal null}.
|
||||
* @return a query String to be used a count query for pagination. Guaranteed to be not {@literal null}.
|
||||
* @since 1.6
|
||||
* @deprecated use {@link DeclaredQuery#deriveCountQuery(String)} instead.
|
||||
* @deprecated use {@link IntrospectedQuery#deriveCountQuery(String)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static String createCountQueryFor(String originalQuery, @Nullable String countProjection) {
|
||||
|
||||
@@ -34,36 +34,21 @@ import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
* @author Mark Paluch
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
final class SimpleJpaQuery extends AbstractStringBasedJpaQuery {
|
||||
|
||||
/**
|
||||
* Creates a new {@link SimpleJpaQuery} encapsulating the query annotated on the given {@link JpaQueryMethod}.
|
||||
*
|
||||
* @param method must not be {@literal null}
|
||||
* @param em must not be {@literal null}
|
||||
* @param countQueryString
|
||||
* @param queryRewriter must not be {@literal null}
|
||||
* @param valueExpressionDelegate must not be {@literal null}
|
||||
*/
|
||||
public SimpleJpaQuery(JpaQueryMethod method, EntityManager em, @Nullable String countQueryString,
|
||||
QueryRewriter queryRewriter, ValueExpressionDelegate valueExpressionDelegate) {
|
||||
this(method, em, method.getRequiredAnnotatedQuery(), countQueryString, queryRewriter, valueExpressionDelegate);
|
||||
}
|
||||
class SimpleJpaQuery extends AbstractStringBasedJpaQuery {
|
||||
|
||||
/**
|
||||
* Creates a new {@link SimpleJpaQuery} that encapsulates a simple query string.
|
||||
*
|
||||
* @param method must not be {@literal null}
|
||||
* @param em must not be {@literal null}
|
||||
* @param queryString must not be {@literal null} or empty
|
||||
* @param countQueryString
|
||||
* @param queryRewriter
|
||||
* @param valueExpressionDelegate must not be {@literal null}
|
||||
* @param method must not be {@literal null}.
|
||||
* @param em must not be {@literal null}.
|
||||
* @param queryString must not be {@literal null} or empty.
|
||||
* @param countQueryString can be {@literal null} if not defined.
|
||||
* @param queryConfiguration must not be {@literal null}.
|
||||
*/
|
||||
public SimpleJpaQuery(JpaQueryMethod method, EntityManager em, String queryString, @Nullable String countQueryString, QueryRewriter queryRewriter,
|
||||
ValueExpressionDelegate valueExpressionDelegate) {
|
||||
public SimpleJpaQuery(JpaQueryMethod method, EntityManager em, String queryString, @Nullable String countQueryString,
|
||||
JpaQueryConfiguration queryConfiguration) {
|
||||
|
||||
super(method, em, queryString, countQueryString, queryRewriter, valueExpressionDelegate);
|
||||
super(method, em, queryString, countQueryString, queryConfiguration);
|
||||
|
||||
validateQuery(getQuery().getQueryString(), "Validation failed for query for method %s", method);
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.util.function.Predicate;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.expression.ValueExpression;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@@ -61,13 +62,14 @@ import org.springframework.util.StringUtils;
|
||||
* @author Greg Turnquist
|
||||
* @author Yuriy Tsarkov
|
||||
*/
|
||||
class StringQuery implements DeclaredQuery {
|
||||
class StringQuery implements EntityQuery {
|
||||
|
||||
private final String query;
|
||||
private final List<ParameterBinding> bindings;
|
||||
private final boolean containsPageableInSpel;
|
||||
private final boolean usesJdbcStyleParameters;
|
||||
private final boolean isNative;
|
||||
private final QueryEnhancerFactory queryEnhancerFactory;
|
||||
private final QueryEnhancer queryEnhancer;
|
||||
private final boolean hasNamedParameters;
|
||||
|
||||
@@ -77,7 +79,7 @@ class StringQuery implements DeclaredQuery {
|
||||
* @param query must not be {@literal null} or empty.
|
||||
*/
|
||||
public StringQuery(String query, boolean isNative) {
|
||||
this(query, isNative, it -> {});
|
||||
this(query, isNative, QueryEnhancerSelector.DEFAULT_SELECTOR, it -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,20 +87,21 @@ class StringQuery implements DeclaredQuery {
|
||||
*
|
||||
* @param query must not be {@literal null} or empty.
|
||||
*/
|
||||
private StringQuery(String query, boolean isNative, Consumer<List<ParameterBinding>> parameterPostProcessor) {
|
||||
StringQuery(String query, boolean isNative, QueryEnhancerFactory factory,Consumer<List<ParameterBinding>> parameterPostProcessor) {
|
||||
|
||||
Assert.hasText(query, "Query must not be null or empty");
|
||||
|
||||
this.isNative = isNative;
|
||||
this.bindings = new ArrayList<>();
|
||||
this.containsPageableInSpel = query.contains("#pageable");
|
||||
this.queryEnhancerFactory = factory;
|
||||
|
||||
Metadata queryMeta = new Metadata();
|
||||
this.query = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query,
|
||||
this.bindings, queryMeta);
|
||||
|
||||
this.usesJdbcStyleParameters = queryMeta.usesJdbcStyleParameters;
|
||||
this.queryEnhancer = QueryEnhancerFactory.forQuery(this);
|
||||
this.queryEnhancer = factory.create(this);
|
||||
|
||||
parameterPostProcessor.accept(this.bindings);
|
||||
|
||||
@@ -113,6 +116,44 @@ class StringQuery implements DeclaredQuery {
|
||||
this.hasNamedParameters = hasNamedParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link StringQuery} from the given JPQL query.
|
||||
*
|
||||
* @param query must not be {@literal null} or empty.
|
||||
*/
|
||||
StringQuery(String query, boolean isNative, QueryEnhancerSelector selector, Consumer<List<ParameterBinding>> parameterPostProcessor) {
|
||||
|
||||
Assert.hasText(query, "Query must not be null or empty");
|
||||
|
||||
this.isNative = isNative;
|
||||
this.bindings = new ArrayList<>();
|
||||
this.containsPageableInSpel = query.contains("#pageable");
|
||||
|
||||
Metadata queryMeta = new Metadata();
|
||||
this.query = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query,
|
||||
this.bindings, queryMeta);
|
||||
|
||||
this.usesJdbcStyleParameters = queryMeta.usesJdbcStyleParameters;
|
||||
this.queryEnhancerFactory = selector.select(this);
|
||||
this.queryEnhancer = queryEnhancerFactory.create(this);
|
||||
|
||||
parameterPostProcessor.accept(this.bindings);
|
||||
|
||||
boolean hasNamedParameters = false;
|
||||
for (ParameterBinding parameterBinding : getParameterBindings()) {
|
||||
if (parameterBinding.getIdentifier().hasName() && parameterBinding.getOrigin().isMethodArgument()) {
|
||||
hasNamedParameters = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.hasNamedParameters = hasNamedParameters;
|
||||
}
|
||||
|
||||
QueryEnhancer getQueryEnhancer() {
|
||||
return queryEnhancer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we have found some like bindings.
|
||||
*/
|
||||
@@ -130,13 +171,13 @@ class StringQuery implements DeclaredQuery {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeclaredQuery deriveCountQuery(@Nullable String countQueryProjection) {
|
||||
public IntrospectedQuery deriveCountQuery(@Nullable String countQueryProjection) {
|
||||
|
||||
// need to copy expression bindings from the declared to the derived query as JPQL query derivation only sees
|
||||
// JPA parameter markers and not the original expressions anymore.
|
||||
|
||||
return new StringQuery(this.queryEnhancer.createCountQueryFor(countQueryProjection), //
|
||||
this.isNative, derivedBindings -> {
|
||||
this.isNative, queryEnhancerFactory, derivedBindings -> {
|
||||
|
||||
// need to copy expression bindings from the declared to the derived query as JPQL query derivation only sees
|
||||
// JPA
|
||||
@@ -158,6 +199,11 @@ class StringQuery implements DeclaredQuery {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String applySorting(Sort sort) {
|
||||
return queryEnhancer.applySorting(sort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean usesJdbcStyleParameters() {
|
||||
return usesJdbcStyleParameters;
|
||||
@@ -168,7 +214,6 @@ class StringQuery implements DeclaredQuery {
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getAlias() {
|
||||
return queryEnhancer.detectAlias();
|
||||
}
|
||||
@@ -404,16 +449,18 @@ class StringQuery implements DeclaredQuery {
|
||||
|
||||
BindingIdentifier targetBinding = queryParameter;
|
||||
Function<BindingIdentifier, ParameterBinding> bindingFactory = switch (ParameterBindingType.of(typeSource)) {
|
||||
case LIKE -> {
|
||||
case LIKE -> {
|
||||
|
||||
Type likeType = LikeParameterBinding.getLikeTypeFrom(matcher.group(2));
|
||||
yield (identifier) -> new LikeParameterBinding(identifier, origin, likeType);
|
||||
}
|
||||
case IN -> (identifier) -> new InParameterBinding(identifier, origin); // fall-through we don't need a special parameter queryParameter for the given parameter.
|
||||
default -> (identifier) -> new ParameterBinding(identifier, origin);
|
||||
};
|
||||
Type likeType = LikeParameterBinding.getLikeTypeFrom(matcher.group(2));
|
||||
yield (identifier) -> new LikeParameterBinding(identifier, origin, likeType);
|
||||
}
|
||||
case IN -> (identifier) -> new InParameterBinding(identifier, origin); // fall-through we don't need a special
|
||||
// parameter queryParameter for the
|
||||
// given parameter.
|
||||
default -> (identifier) -> new ParameterBinding(identifier, origin);
|
||||
};
|
||||
|
||||
if (origin.isExpression()) {
|
||||
if (origin.isExpression()) {
|
||||
parameterBindings.register(bindingFactory.apply(queryParameter));
|
||||
} else {
|
||||
targetBinding = parameterBindings.register(queryParameter, origin, bindingFactory, parameterLabels);
|
||||
|
||||
@@ -35,17 +35,8 @@ 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.BeanFactoryQueryRewriterProvider;
|
||||
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.QueryRewriterProvider;
|
||||
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,12 +73,12 @@ import com.querydsl.core.types.EntityPath;
|
||||
public class JpaRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
private final EntityManager entityManager;
|
||||
private final QueryExtractor extractor;
|
||||
private final CrudMethodMetadataPostProcessor crudMethodMetadataPostProcessor;
|
||||
private final CrudMethodMetadata crudMethodMetadata;
|
||||
|
||||
private EntityPathResolver entityPathResolver;
|
||||
private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT;
|
||||
private QueryEnhancerSelector queryEnhancerSelector = QueryEnhancerSelector.DEFAULT_SELECTOR;
|
||||
private JpaQueryMethodFactory queryMethodFactory;
|
||||
private QueryRewriterProvider queryRewriterProvider;
|
||||
|
||||
@@ -101,7 +92,7 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
|
||||
Assert.notNull(entityManager, "EntityManager must not be null");
|
||||
|
||||
this.entityManager = entityManager;
|
||||
this.extractor = PersistenceProvider.fromEntityManager(entityManager);
|
||||
PersistenceProvider extractor = PersistenceProvider.fromEntityManager(entityManager);
|
||||
this.crudMethodMetadataPostProcessor = new CrudMethodMetadataPostProcessor();
|
||||
this.entityPathResolver = SimpleEntityPathResolver.INSTANCE;
|
||||
this.queryMethodFactory = new DefaultJpaQueryMethodFactory(extractor);
|
||||
@@ -179,6 +170,19 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
|
||||
this.queryMethodFactory = queryMethodFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link QueryEnhancerSelector} to be used. Defaults to
|
||||
* {@link QueryEnhancerSelector#DEFAULT_SELECTOR}.
|
||||
*
|
||||
* @param queryEnhancerSelector must not be {@literal null}.
|
||||
*/
|
||||
public void setQueryEnhancerSelector(QueryEnhancerSelector queryEnhancerSelector) {
|
||||
|
||||
Assert.notNull(queryEnhancerSelector, "QueryEnhancerSelector must not be null");
|
||||
|
||||
this.queryEnhancerSelector = queryEnhancerSelector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link QueryRewriterProvider} to be used. Defaults to instantiate query rewriters through
|
||||
* {@link BeanUtils#instantiateClass(Class)}.
|
||||
@@ -243,8 +247,12 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport {
|
||||
@Override
|
||||
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
|
||||
ValueExpressionDelegate valueExpressionDelegate) {
|
||||
|
||||
JpaQueryConfiguration queryConfiguration = new JpaQueryConfiguration(queryRewriterProvider, queryEnhancerSelector,
|
||||
new CachingValueExpressionDelegate(valueExpressionDelegate), escapeCharacter);
|
||||
|
||||
return Optional.of(JpaQueryLookupStrategy.create(entityManager, queryMethodFactory, key,
|
||||
new CachingValueExpressionDelegate(valueExpressionDelegate), queryRewriterProvider, escapeCharacter));
|
||||
queryConfiguration));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,12 +18,18 @@ package org.springframework.data.jpa.repository.support;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.data.jpa.repository.query.EscapeCharacter;
|
||||
import org.springframework.data.jpa.repository.query.JpaQueryMethodFactory;
|
||||
import org.springframework.data.jpa.repository.query.QueryEnhancerSelector;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.querydsl.EntityPathResolver;
|
||||
import org.springframework.data.querydsl.SimpleEntityPathResolver;
|
||||
@@ -46,10 +52,12 @@ import org.springframework.util.Assert;
|
||||
public class JpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
|
||||
extends TransactionalRepositoryFactoryBeanSupport<T, S, ID> {
|
||||
|
||||
private @Nullable BeanFactory beanFactory;
|
||||
private @Nullable EntityManager entityManager;
|
||||
private EntityPathResolver entityPathResolver;
|
||||
private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT;
|
||||
private @Nullable JpaQueryMethodFactory queryMethodFactory;
|
||||
private @Nullable Function<BeanFactory, QueryEnhancerSelector> queryEnhancerSelectorSource;
|
||||
|
||||
/**
|
||||
* Creates a new {@link JpaRepositoryFactoryBean} for the given repository interface.
|
||||
@@ -75,6 +83,12 @@ public class JpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
|
||||
super.setMappingContext(mappingContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
super.setBeanFactory(beanFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link EntityPathResolver} to be used. Will expect a canonical bean to be present but fallback to
|
||||
* {@link SimpleEntityPathResolver#INSTANCE} in case none is available.
|
||||
@@ -101,6 +115,43 @@ public class JpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link QueryEnhancerSelector} to be used. Defaults to
|
||||
* {@link QueryEnhancerSelector#DEFAULT_SELECTOR}.
|
||||
*
|
||||
* @param queryEnhancerSelectorSource must not be {@literal null}.
|
||||
*/
|
||||
public void setQueryEnhancerSelectorSource(QueryEnhancerSelector queryEnhancerSelectorSource) {
|
||||
this.queryEnhancerSelectorSource = bf -> queryEnhancerSelectorSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the {@link QueryEnhancerSelector} to be used.
|
||||
*
|
||||
* @param queryEnhancerSelectorType must not be {@literal null}.
|
||||
*/
|
||||
public void setQueryEnhancerSelector(Class<? extends QueryEnhancerSelector> queryEnhancerSelectorType) {
|
||||
|
||||
this.queryEnhancerSelectorSource = bf -> {
|
||||
|
||||
if (bf != null) {
|
||||
|
||||
ObjectProvider<? extends QueryEnhancerSelector> beanProvider = bf.getBeanProvider(queryEnhancerSelectorType);
|
||||
QueryEnhancerSelector selector = beanProvider.getIfAvailable();
|
||||
|
||||
if (selector != null) {
|
||||
return selector;
|
||||
}
|
||||
|
||||
if (bf instanceof AutowireCapableBeanFactory acbf) {
|
||||
return acbf.createBean(queryEnhancerSelectorType);
|
||||
}
|
||||
}
|
||||
|
||||
return BeanUtils.instantiateClass(queryEnhancerSelectorType);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RepositoryFactorySupport doCreateRepositoryFactory() {
|
||||
|
||||
@@ -114,15 +165,19 @@ public class JpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
|
||||
*/
|
||||
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
|
||||
|
||||
JpaRepositoryFactory jpaRepositoryFactory = new JpaRepositoryFactory(entityManager);
|
||||
jpaRepositoryFactory.setEntityPathResolver(entityPathResolver);
|
||||
jpaRepositoryFactory.setEscapeCharacter(escapeCharacter);
|
||||
JpaRepositoryFactory factory = new JpaRepositoryFactory(entityManager);
|
||||
factory.setEntityPathResolver(entityPathResolver);
|
||||
factory.setEscapeCharacter(escapeCharacter);
|
||||
|
||||
if (queryMethodFactory != null) {
|
||||
jpaRepositoryFactory.setQueryMethodFactory(queryMethodFactory);
|
||||
factory.setQueryMethodFactory(queryMethodFactory);
|
||||
}
|
||||
|
||||
return jpaRepositoryFactory;
|
||||
if (queryEnhancerSelectorSource != null) {
|
||||
factory.setQueryEnhancerSelector(queryEnhancerSelectorSource.apply(beanFactory));
|
||||
}
|
||||
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -34,7 +34,6 @@ 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.jpa.repository.QueryRewriter;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
@@ -53,6 +52,9 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
@ContextConfiguration("classpath:infrastructure.xml")
|
||||
class AbstractStringBasedJpaQueryIntegrationTests {
|
||||
|
||||
private static final JpaQueryConfiguration CONFIG = new JpaQueryConfiguration(QueryRewriterProvider.simple(),
|
||||
QueryEnhancerSelector.DEFAULT_SELECTOR, ValueExpressionDelegate.create(), EscapeCharacter.DEFAULT);
|
||||
|
||||
@PersistenceContext EntityManager em;
|
||||
|
||||
@Autowired BeanFactory beanFactory;
|
||||
@@ -66,8 +68,7 @@ class AbstractStringBasedJpaQueryIntegrationTests {
|
||||
when(mock.getMetamodel()).thenReturn(em.getMetamodel());
|
||||
|
||||
JpaQueryMethod method = getMethod("findRolesByEmailAddress", String.class);
|
||||
AbstractStringBasedJpaQuery jpaQuery = new SimpleJpaQuery(method, mock, null, QueryRewriter.IdentityQueryRewriter.INSTANCE,
|
||||
ValueExpressionDelegate.create());
|
||||
AbstractStringBasedJpaQuery jpaQuery = new SimpleJpaQuery(method, mock, method.getAnnotatedQuery(), null, CONFIG);
|
||||
|
||||
jpaQuery.createJpaQuery(method.getAnnotatedQuery(), Sort.unsorted(), null,
|
||||
method.getResultProcessor().getReturnedType());
|
||||
|
||||
@@ -36,7 +36,6 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.provider.QueryExtractor;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.jpa.repository.QueryRewriter;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
@@ -56,6 +55,9 @@ import org.springframework.util.ReflectionUtils;
|
||||
*/
|
||||
class AbstractStringBasedJpaQueryUnitTests {
|
||||
|
||||
private static final JpaQueryConfiguration CONFIG = new JpaQueryConfiguration(QueryRewriterProvider.simple(),
|
||||
QueryEnhancerSelector.DEFAULT_SELECTOR, ValueExpressionDelegate.create(), EscapeCharacter.DEFAULT);
|
||||
|
||||
@Test // GH-3310
|
||||
void shouldNotAttemptToAppendSortIfNoSortArgumentPresent() {
|
||||
|
||||
@@ -118,8 +120,8 @@ class AbstractStringBasedJpaQueryUnitTests {
|
||||
|
||||
Query query = AnnotatedElementUtils.getMergedAnnotation(respositoryMethod, Query.class);
|
||||
|
||||
return new InvocationCapturingStringQueryStub(respositoryMethod, queryMethod, query.value(), query.countQuery());
|
||||
|
||||
return new InvocationCapturingStringQueryStub(respositoryMethod, queryMethod, query.value(), query.countQuery(),
|
||||
CONFIG);
|
||||
}
|
||||
|
||||
static class InvocationCapturingStringQueryStub extends AbstractStringBasedJpaQuery {
|
||||
@@ -128,7 +130,7 @@ class AbstractStringBasedJpaQueryUnitTests {
|
||||
private final MultiValueMap<String, Arguments> capturedArguments = new LinkedMultiValueMap<>(3);
|
||||
|
||||
InvocationCapturingStringQueryStub(Method targetMethod, JpaQueryMethod queryMethod, String queryString,
|
||||
@Nullable String countQueryString) {
|
||||
@Nullable String countQueryString, JpaQueryConfiguration queryConfiguration) {
|
||||
super(queryMethod, new Supplier<EntityManager>() {
|
||||
|
||||
@Override
|
||||
@@ -142,8 +144,7 @@ class AbstractStringBasedJpaQueryUnitTests {
|
||||
|
||||
return em;
|
||||
}
|
||||
}.get(), queryString, countQueryString, Mockito.mock(QueryRewriter.class),
|
||||
ValueExpressionDelegate.create());
|
||||
}.get(), queryString, countQueryString, queryConfiguration);
|
||||
|
||||
this.targetMethod = targetMethod;
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ import org.springframework.data.domain.Sort;
|
||||
class DefaultQueryEnhancerUnitTests extends QueryEnhancerTckTests {
|
||||
|
||||
@Override
|
||||
QueryEnhancer createQueryEnhancer(DeclaredQuery declaredQuery) {
|
||||
return new DefaultQueryEnhancer(declaredQuery);
|
||||
QueryEnhancer createQueryEnhancer(DeclaredQuery query) {
|
||||
return new DefaultQueryEnhancer(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,7 +43,7 @@ class DefaultQueryEnhancerUnitTests extends QueryEnhancerTckTests {
|
||||
@Test // GH-3546
|
||||
void shouldApplySorting() {
|
||||
|
||||
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.of("SELECT e FROM Employee e", true));
|
||||
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.ofNative("SELECT e FROM Employee e"));
|
||||
|
||||
String sql = enhancer.applySorting(Sort.by("foo", "bar"));
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ public class EqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
|
||||
|
||||
assumeThat(query.isNativeQuery()).isFalse();
|
||||
|
||||
return JpaQueryEnhancer.forEql(query);
|
||||
return JpaQueryEnhancer.forEql(query.getQueryString());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -827,6 +827,6 @@ class EqlQueryTransformerTests {
|
||||
}
|
||||
|
||||
private QueryEnhancer newParser(String query) {
|
||||
return JpaQueryEnhancer.forEql(DeclaredQuery.of(query, false));
|
||||
return JpaQueryEnhancer.forEql(query);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
|
||||
import org.springframework.data.expression.ValueExpressionParser;
|
||||
import org.springframework.data.jpa.repository.query.ParameterBinding.LikeParameterBinding;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.data.repository.query.parser.Part.Type;
|
||||
|
||||
/**
|
||||
@@ -47,7 +47,9 @@ import org.springframework.data.repository.query.parser.Part.Type;
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class ExpressionBasedStringQueryUnitTests {
|
||||
|
||||
private static final ValueExpressionParser PARSER = ValueExpressionParser.create();
|
||||
private static final JpaQueryConfiguration CONFIG = new JpaQueryConfiguration(QueryRewriterProvider.simple(),
|
||||
QueryEnhancerSelector.DEFAULT_SELECTOR, ValueExpressionDelegate.create(), EscapeCharacter.DEFAULT);
|
||||
|
||||
@Mock JpaEntityMetadata<?> metadata;
|
||||
|
||||
@BeforeEach
|
||||
@@ -59,14 +61,16 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
void shouldReturnQueryWithDomainTypeExpressionReplacedWithSimpleDomainTypeName() {
|
||||
|
||||
String source = "select u from #{#entityName} u where u.firstname like :firstname";
|
||||
StringQuery query = new ExpressionBasedStringQuery(source, metadata, PARSER, false);
|
||||
StringQuery query = new ExpressionBasedStringQuery(source, metadata,
|
||||
CONFIG.getValueExpressionDelegate().getValueExpressionParser(), false, CONFIG.getSelector());
|
||||
assertThat(query.getQueryString()).isEqualTo("select u from User u where u.firstname like :firstname");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-424
|
||||
void renderAliasInExpressionQueryCorrectly() {
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery("select u from #{#entityName} u", metadata, PARSER, true);
|
||||
StringQuery query = new ExpressionBasedStringQuery("select u from #{#entityName} u", metadata,
|
||||
CONFIG.getValueExpressionDelegate().getValueExpressionParser(), true, CONFIG.getSelector());
|
||||
assertThat(query.getAlias()).isEqualTo("u");
|
||||
assertThat(query.getQueryString()).isEqualTo("select u from User u");
|
||||
}
|
||||
@@ -79,7 +83,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
+ "AND (LOWER(n.server) LIKE LOWER(:#{#networkRequest.server})) OR :#{#networkRequest.server} IS NULL "
|
||||
+ "AND (n.createdAt >= :#{#networkRequest.createdTime.startDateTime}) AND (n.createdAt <=:#{#networkRequest.createdTime.endDateTime}) "
|
||||
+ "AND (n.updatedAt >= :#{#networkRequest.updatedTime.startDateTime}) AND (n.updatedAt <=:#{#networkRequest.updatedTime.endDateTime})",
|
||||
metadata, PARSER, false);
|
||||
metadata, CONFIG.getValueExpressionDelegate().getValueExpressionParser(), false, CONFIG.getSelector());
|
||||
|
||||
assertThat(query.getParameterBindings()).hasSize(8);
|
||||
}
|
||||
@@ -92,7 +96,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
+ "AND (LOWER(n.server) LIKE LOWER(NULLIF(text(concat('%',?#{#networkRequest.server},'%')), '')) OR ?#{#networkRequest.server} IS NULL)"
|
||||
+ "AND (n.createdAt >= ?#{#networkRequest.createdTime.startDateTime}) AND (n.createdAt <=?#{#networkRequest.createdTime.endDateTime})"
|
||||
+ "AND (n.updatedAt >= ?#{#networkRequest.updatedTime.startDateTime}) AND (n.updatedAt <=?#{#networkRequest.updatedTime.endDateTime})",
|
||||
metadata, PARSER, false);
|
||||
metadata, CONFIG.getValueExpressionDelegate().getValueExpressionParser(), false, CONFIG.getSelector());
|
||||
|
||||
assertThat(query.getParameterBindings()).hasSize(8);
|
||||
}
|
||||
@@ -105,7 +109,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
+ "AND (LOWER(n.server) LIKE LOWER(NULLIF(text(concat('%',?#{#networkRequest.server},'%')), '')) OR ?#{#networkRequest.server} IS NULL)"
|
||||
+ "AND (n.createdAt >= ?#{#networkRequest.createdTime.startDateTime}) AND (n.createdAt <=?#{#networkRequest.createdTime.endDateTime})"
|
||||
+ "AND (n.updatedAt >= ?#{#networkRequest.updatedTime.startDateTime}) AND (n.updatedAt <=?#{#networkRequest.updatedTime.endDateTime})",
|
||||
metadata, PARSER, true);
|
||||
metadata, CONFIG.getValueExpressionDelegate().getValueExpressionParser(), false, CONFIG.getSelector());
|
||||
|
||||
assertThat(query.isNativeQuery()).isFalse();
|
||||
}
|
||||
@@ -113,7 +117,8 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
@Test
|
||||
void shouldDetectSimpleNativeQueriesWithSpelAsNonNative() {
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery("select n from #{#entityName} n", metadata, PARSER, true);
|
||||
StringQuery query = new ExpressionBasedStringQuery("select n from #{#entityName} n", metadata,
|
||||
CONFIG.getValueExpressionDelegate().getValueExpressionParser(), true, CONFIG.getSelector());
|
||||
|
||||
assertThat(query.isNativeQuery()).isFalse();
|
||||
}
|
||||
@@ -121,7 +126,8 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
@Test
|
||||
void shouldDetectSimpleNativeQueriesWithoutSpelAsNative() {
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery("select u from User u", metadata, PARSER, true);
|
||||
StringQuery query = new ExpressionBasedStringQuery("select u from User u", metadata,
|
||||
CONFIG.getValueExpressionDelegate().getValueExpressionParser(), true, CONFIG.getSelector());
|
||||
|
||||
assertThat(query.isNativeQuery()).isTrue();
|
||||
}
|
||||
@@ -130,8 +136,8 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
void namedExpressionsShouldCreateLikeBindings() {
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery(
|
||||
"select u from User u where u.firstname like %:#{foo} or u.firstname like :#{foo}%", metadata, PARSER,
|
||||
false);
|
||||
"select u from User u where u.firstname like %:#{foo} or u.firstname like :#{foo}%", metadata,
|
||||
CONFIG.getValueExpressionDelegate().getValueExpressionParser(), false, CONFIG.getSelector());
|
||||
|
||||
assertThat(query.hasParameterBindings()).isTrue();
|
||||
assertThat(query.getQueryString()).isEqualTo(
|
||||
@@ -155,8 +161,8 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
void indexedExpressionsShouldCreateLikeBindings() {
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery(
|
||||
"select u from User u where u.firstname like %?#{foo} or u.firstname like ?#{foo}%", metadata, PARSER,
|
||||
false);
|
||||
"select u from User u where u.firstname like %?#{foo} or u.firstname like ?#{foo}%", metadata,
|
||||
CONFIG.getValueExpressionDelegate().getValueExpressionParser(), false, CONFIG.getSelector());
|
||||
|
||||
assertThat(query.hasParameterBindings()).isTrue();
|
||||
assertThat(query.getQueryString())
|
||||
@@ -180,7 +186,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
void doesTemplatingWhenEntityNameSpelIsPresent() {
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery("select #{#entityName + 'Hallo'} from #{#entityName} u",
|
||||
metadata, PARSER, false);
|
||||
metadata, CONFIG.getValueExpressionDelegate().getValueExpressionParser(), false, CONFIG.getSelector());
|
||||
|
||||
assertThat(query.getQueryString()).isEqualTo("select UserHallo from User u");
|
||||
}
|
||||
@@ -189,7 +195,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
void doesNoTemplatingWhenEntityNameSpelIsNotPresent() {
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery("select #{#entityName + 'Hallo'} from User u", metadata,
|
||||
PARSER, false);
|
||||
CONFIG.getValueExpressionDelegate().getValueExpressionParser(), false, CONFIG.getSelector());
|
||||
|
||||
assertThat(query.getQueryString()).isEqualTo("select UserHallo from User u");
|
||||
}
|
||||
@@ -198,7 +204,7 @@ class ExpressionBasedStringQueryUnitTests {
|
||||
void doesTemplatingWhenEntityNameSpelIsPresentForBindParameter() {
|
||||
|
||||
StringQuery query = new ExpressionBasedStringQuery("select u from #{#entityName} u where name = :#{#something}",
|
||||
metadata, PARSER, false);
|
||||
metadata, CONFIG.getValueExpressionDelegate().getValueExpressionParser(), false, CONFIG.getSelector());
|
||||
|
||||
assertThat(query.getQueryString()).isEqualTo("select u from User u where name = :__$synthetic$__1");
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public class HqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
|
||||
|
||||
assumeThat(query.isNativeQuery()).isFalse();
|
||||
|
||||
return JpaQueryEnhancer.forHql(query);
|
||||
return JpaQueryEnhancer.forHql(query.getQueryString());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1196,6 +1196,6 @@ class HqlQueryTransformerTests {
|
||||
}
|
||||
|
||||
private QueryEnhancer newParser(String query) {
|
||||
return JpaQueryEnhancer.forHql(DeclaredQuery.of(query, false));
|
||||
return JpaQueryEnhancer.forHql(query);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,14 +39,14 @@ import org.springframework.data.repository.query.ReturnedType;
|
||||
class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
|
||||
|
||||
@Override
|
||||
QueryEnhancer createQueryEnhancer(DeclaredQuery declaredQuery) {
|
||||
return new JSqlParserQueryEnhancer(declaredQuery);
|
||||
QueryEnhancer createQueryEnhancer(DeclaredQuery query) {
|
||||
return new JSqlParserQueryEnhancer(query);
|
||||
}
|
||||
|
||||
@Test // GH-3546
|
||||
void shouldApplySorting() {
|
||||
|
||||
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.of("SELECT e FROM Employee e", true));
|
||||
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.ofJpql("SELECT e FROM Employee e"));
|
||||
|
||||
String sql = enhancer.applySorting(Sort.by("foo", "bar"));
|
||||
|
||||
@@ -69,13 +69,13 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
|
||||
@Test // GH-3707
|
||||
void countQueriesShouldConsiderPrimaryTableAlias() {
|
||||
|
||||
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.of("""
|
||||
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.ofNative("""
|
||||
SELECT DISTINCT a.*, b.b1
|
||||
FROM TableA a
|
||||
JOIN TableB b ON a.b = b.b
|
||||
LEFT JOIN TableC c ON b.c = c.c
|
||||
ORDER BY b.b1, a.a1, a.a2
|
||||
""", true));
|
||||
"""));
|
||||
|
||||
String sql = enhancer.createCountQueryFor();
|
||||
|
||||
@@ -98,7 +98,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
|
||||
+ "select SOME_COLUMN from SOME_OTHER_TABLE where REPORTING_DATE = :REPORTING_DATE";
|
||||
|
||||
StringQuery stringQuery = new StringQuery(setQuery, true);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery).create(stringQuery);
|
||||
|
||||
assertThat(stringQuery.getAlias()).isNullOrEmpty();
|
||||
assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("SOME_COLUMN");
|
||||
@@ -121,7 +121,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
|
||||
+ "union select SOME_COLUMN from SOME_OTHER_OTHER_TABLE";
|
||||
|
||||
StringQuery stringQuery = new StringQuery(setQuery, true);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery).create(stringQuery);
|
||||
|
||||
assertThat(stringQuery.getAlias()).isNullOrEmpty();
|
||||
assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("SOME_COLUMN");
|
||||
@@ -148,7 +148,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
|
||||
+ "\t;";
|
||||
|
||||
StringQuery stringQuery = new StringQuery(setQuery, true);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery).create(stringQuery);
|
||||
|
||||
assertThat(stringQuery.getAlias()).isNullOrEmpty();
|
||||
assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("CustomerID");
|
||||
@@ -168,7 +168,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
|
||||
String setQuery = "VALUES (1, 2, 'test')";
|
||||
|
||||
StringQuery stringQuery = new StringQuery(setQuery, true);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery).create(stringQuery);
|
||||
|
||||
assertThat(stringQuery.getAlias()).isNullOrEmpty();
|
||||
assertThat(stringQuery.getProjection()).isNullOrEmpty();
|
||||
@@ -189,7 +189,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
|
||||
+ "select day, value from sample_data as a";
|
||||
|
||||
StringQuery stringQuery = new StringQuery(setQuery, true);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery).create(stringQuery);
|
||||
|
||||
assertThat(stringQuery.getAlias()).isEqualToIgnoringCase("a");
|
||||
assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("day, value");
|
||||
@@ -212,7 +212,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
|
||||
+ "select day, value from sample_data as a";
|
||||
|
||||
StringQuery stringQuery = new StringQuery(setQuery, true);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery).create(stringQuery);
|
||||
|
||||
assertThat(stringQuery.getAlias()).isEqualToIgnoringCase("a");
|
||||
assertThat(stringQuery.getProjection()).isEqualToIgnoringCase("day, value");
|
||||
@@ -232,7 +232,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
|
||||
void truncateStatementShouldWork() {
|
||||
|
||||
StringQuery stringQuery = new StringQuery("TRUNCATE TABLE foo", true);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery).create(stringQuery);
|
||||
|
||||
assertThat(stringQuery.getAlias()).isNull();
|
||||
assertThat(stringQuery.getProjection()).isEmpty();
|
||||
@@ -250,7 +250,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
|
||||
void mergeStatementWorksWithJSqlParser(String query, String alias) {
|
||||
|
||||
StringQuery stringQuery = new StringQuery(query, true);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery).create(stringQuery);
|
||||
|
||||
assertThat(queryEnhancer.detectAlias()).isEqualTo(alias);
|
||||
assertThat(QueryUtils.detectAlias(query)).isNull();
|
||||
|
||||
@@ -34,7 +34,6 @@ 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;
|
||||
@@ -64,7 +63,8 @@ import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class JpaQueryLookupStrategyUnitTests {
|
||||
|
||||
private static final ValueExpressionDelegate VALUE_EXPRESSION_DELEGATE = ValueExpressionDelegate.create();
|
||||
private static final JpaQueryConfiguration CONFIG = new JpaQueryConfiguration(QueryRewriterProvider.simple(),
|
||||
QueryEnhancerSelector.DEFAULT_SELECTOR, ValueExpressionDelegate.create(), EscapeCharacter.DEFAULT);
|
||||
|
||||
@Mock EntityManager em;
|
||||
@Mock EntityManagerFactory emf;
|
||||
@@ -72,7 +72,6 @@ class JpaQueryLookupStrategyUnitTests {
|
||||
@Mock NamedQueries namedQueries;
|
||||
@Mock Metamodel metamodel;
|
||||
@Mock ProjectionFactory projectionFactory;
|
||||
@Mock BeanFactory beanFactory;
|
||||
|
||||
private JpaQueryMethodFactory queryMethodFactory;
|
||||
|
||||
@@ -90,7 +89,7 @@ class JpaQueryLookupStrategyUnitTests {
|
||||
void invalidAnnotatedQueryCausesException() throws Exception {
|
||||
|
||||
QueryLookupStrategy strategy = JpaQueryLookupStrategy.create(em, queryMethodFactory, Key.CREATE_IF_NOT_FOUND,
|
||||
VALUE_EXPRESSION_DELEGATE, new BeanFactoryQueryRewriterProvider(beanFactory), EscapeCharacter.DEFAULT);
|
||||
CONFIG);
|
||||
Method method = UserRepository.class.getMethod("findByFoo", String.class);
|
||||
RepositoryMetadata metadata = new DefaultRepositoryMetadata(UserRepository.class);
|
||||
|
||||
@@ -102,7 +101,7 @@ class JpaQueryLookupStrategyUnitTests {
|
||||
void considersNamedCountQuery() throws Exception {
|
||||
|
||||
QueryLookupStrategy strategy = JpaQueryLookupStrategy.create(em, queryMethodFactory, Key.CREATE_IF_NOT_FOUND,
|
||||
VALUE_EXPRESSION_DELEGATE, new BeanFactoryQueryRewriterProvider(beanFactory), EscapeCharacter.DEFAULT);
|
||||
CONFIG);
|
||||
|
||||
when(namedQueries.hasQuery("foo.count")).thenReturn(true);
|
||||
when(namedQueries.getQuery("foo.count")).thenReturn("select count(foo) from Foo foo");
|
||||
@@ -124,7 +123,7 @@ class JpaQueryLookupStrategyUnitTests {
|
||||
void considersNamedCountOnStringQueryQuery() throws Exception {
|
||||
|
||||
QueryLookupStrategy strategy = JpaQueryLookupStrategy.create(em, queryMethodFactory, Key.CREATE_IF_NOT_FOUND,
|
||||
VALUE_EXPRESSION_DELEGATE, new BeanFactoryQueryRewriterProvider(beanFactory), EscapeCharacter.DEFAULT);
|
||||
CONFIG);
|
||||
|
||||
when(namedQueries.hasQuery("foo.count")).thenReturn(true);
|
||||
when(namedQueries.getQuery("foo.count")).thenReturn("select count(foo) from Foo foo");
|
||||
@@ -143,7 +142,7 @@ class JpaQueryLookupStrategyUnitTests {
|
||||
void prefersDeclaredQuery() throws Exception {
|
||||
|
||||
QueryLookupStrategy strategy = JpaQueryLookupStrategy.create(em, queryMethodFactory, Key.CREATE_IF_NOT_FOUND,
|
||||
VALUE_EXPRESSION_DELEGATE, new BeanFactoryQueryRewriterProvider(beanFactory), EscapeCharacter.DEFAULT);
|
||||
CONFIG);
|
||||
Method method = UserRepository.class.getMethod("annotatedQueryWithQueryAndQueryName");
|
||||
RepositoryMetadata metadata = new DefaultRepositoryMetadata(UserRepository.class);
|
||||
|
||||
@@ -156,7 +155,7 @@ class JpaQueryLookupStrategyUnitTests {
|
||||
void namedQueryWithSortShouldThrowIllegalStateException() throws NoSuchMethodException {
|
||||
|
||||
QueryLookupStrategy strategy = JpaQueryLookupStrategy.create(em, queryMethodFactory, Key.CREATE_IF_NOT_FOUND,
|
||||
VALUE_EXPRESSION_DELEGATE, new BeanFactoryQueryRewriterProvider(beanFactory), EscapeCharacter.DEFAULT);
|
||||
CONFIG);
|
||||
|
||||
Method method = UserRepository.class.getMethod("customNamedQuery", String.class, Sort.class);
|
||||
RepositoryMetadata metadata = new DefaultRepositoryMetadata(UserRepository.class);
|
||||
@@ -181,7 +180,7 @@ class JpaQueryLookupStrategyUnitTests {
|
||||
void customQueryWithQuestionMarksShouldWork() throws NoSuchMethodException {
|
||||
|
||||
QueryLookupStrategy strategy = JpaQueryLookupStrategy.create(em, queryMethodFactory, Key.CREATE_IF_NOT_FOUND,
|
||||
VALUE_EXPRESSION_DELEGATE, new BeanFactoryQueryRewriterProvider(beanFactory), EscapeCharacter.DEFAULT);
|
||||
CONFIG);
|
||||
|
||||
Method namedMethod = UserRepository.class.getMethod("customQueryWithQuestionMarksAndNamedParam", String.class);
|
||||
RepositoryMetadata namedMetadata = new DefaultRepositoryMetadata(UserRepository.class);
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -27,6 +26,7 @@ import java.util.Set;
|
||||
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;
|
||||
@@ -43,8 +43,11 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
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.data.jpa.repository.support.JpaRepositoryFactory;
|
||||
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
/**
|
||||
* Unit tests for repository with {@link Query} and {@link QueryRewriter}.
|
||||
@@ -57,6 +60,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
class JpaQueryRewriteIntegrationTests {
|
||||
|
||||
@Autowired private UserRepositoryWithRewriter repository;
|
||||
@Autowired private JpaRepositoryFactoryBean<UserRepositoryWithRewriter, User, Integer> factoryBean;
|
||||
|
||||
// Results
|
||||
static final String ORIGINAL_QUERY = "original query";
|
||||
@@ -71,6 +75,14 @@ class JpaQueryRewriteIntegrationTests {
|
||||
repository.deleteAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConfigureQueryEnhancerSelector() {
|
||||
|
||||
JpaRepositoryFactory factory = (JpaRepositoryFactory) ReflectionTestUtils.getField(factoryBean, "factory");
|
||||
|
||||
assertThat(factory).extracting("queryEnhancerSelector").isInstanceOf(MyQueryEnhancerSelector.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nativeQueryShouldHandleRewrites() {
|
||||
|
||||
@@ -228,7 +240,8 @@ class JpaQueryRewriteIntegrationTests {
|
||||
@ImportResource("classpath:infrastructure.xml")
|
||||
@EnableJpaRepositories(considerNestedRepositories = true, basePackageClasses = UserRepositoryWithRewriter.class, //
|
||||
includeFilters = @ComponentScan.Filter(value = { UserRepositoryWithRewriter.class },
|
||||
type = FilterType.ASSIGNABLE_TYPE))
|
||||
type = FilterType.ASSIGNABLE_TYPE),
|
||||
queryEnhancerSelector = MyQueryEnhancerSelector.class)
|
||||
static class JpaRepositoryConfig {
|
||||
|
||||
@Bean
|
||||
@@ -237,4 +250,10 @@ class JpaQueryRewriteIntegrationTests {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MyQueryEnhancerSelector extends QueryEnhancerSelector.DefaultQueryEnhancerSelector {
|
||||
public MyQueryEnhancerSelector() {
|
||||
super(QueryEnhancerFactories.fallback(), DefaultQueryEnhancerSelector.jpql());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public class JpqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests {
|
||||
|
||||
assumeThat(query.isNativeQuery()).isFalse();
|
||||
|
||||
return JpaQueryEnhancer.forJpql(query);
|
||||
return JpaQueryEnhancer.forJpql(query.getQueryString());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -832,6 +832,6 @@ class JpqlQueryTransformerTests {
|
||||
}
|
||||
|
||||
private QueryEnhancer newParser(String query) {
|
||||
return JpaQueryEnhancer.forJpql(DeclaredQuery.of(query, false));
|
||||
return JpaQueryEnhancer.forJpql(query);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,8 +89,7 @@ class NamedQueryUnitTests {
|
||||
JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, projectionFactory, extractor);
|
||||
|
||||
when(em.createNamedQuery(queryMethod.getNamedCountQueryName())).thenThrow(new IllegalArgumentException());
|
||||
assertThatExceptionOfType(QueryCreationException.class)
|
||||
.isThrownBy(() -> NamedQuery.lookupFrom(queryMethod, em, QueryRewriter.IdentityQueryRewriter.INSTANCE));
|
||||
assertThatExceptionOfType(QueryCreationException.class).isThrownBy(() -> NamedQuery.lookupFrom(queryMethod, em, QueryEnhancerSelector.DEFAULT_SELECTOR, QueryRewriter.IdentityQueryRewriter.INSTANCE));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-142
|
||||
@@ -102,8 +101,7 @@ class NamedQueryUnitTests {
|
||||
|
||||
TypedQuery<Long> countQuery = mock(TypedQuery.class);
|
||||
when(em.createNamedQuery(eq(queryMethod.getNamedCountQueryName()), eq(Long.class))).thenReturn(countQuery);
|
||||
NamedQuery query = (NamedQuery) NamedQuery.lookupFrom(queryMethod, em,
|
||||
QueryRewriter.IdentityQueryRewriter.INSTANCE);
|
||||
NamedQuery query = (NamedQuery) NamedQuery.lookupFrom(queryMethod, em, QueryEnhancerSelector.DEFAULT_SELECTOR, QueryRewriter.IdentityQueryRewriter.INSTANCE);
|
||||
|
||||
query.doCreateCountQuery(new JpaParametersParameterAccessor(queryMethod.getParameters(), new Object[1]));
|
||||
verify(em, times(1)).createNamedQuery(queryMethod.getNamedCountQueryName(), Long.class);
|
||||
|
||||
@@ -34,7 +34,6 @@ import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.provider.QueryExtractor;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.jpa.repository.QueryRewriter;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
@@ -75,7 +74,8 @@ class NativeJpaQueryUnitTests {
|
||||
Query annotation = AnnotatedElementUtils.getMergedAnnotation(respositoryMethod, Query.class);
|
||||
|
||||
NativeJpaQuery query = new NativeJpaQuery(queryMethod, em, annotation.value(), annotation.countQuery(),
|
||||
QueryRewriter.IdentityQueryRewriter.INSTANCE, ValueExpressionDelegate.create());
|
||||
new JpaQueryConfiguration(QueryRewriterProvider.simple(), QueryEnhancerSelector.DEFAULT_SELECTOR,
|
||||
ValueExpressionDelegate.create(), EscapeCharacter.DEFAULT));
|
||||
String sql = query.getSortedQueryString(Sort.by("foo", "bar"), queryMethod.getResultProcessor().getReturnedType());
|
||||
|
||||
assertThat(sql).isEqualTo("SELECT e FROM Employee e order by e.foo asc, e.bar asc");
|
||||
|
||||
@@ -17,16 +17,7 @@ package org.springframework.data.jpa.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import org.springframework.data.jpa.repository.query.QueryEnhancerFactory.NativeQueryEnhancer;
|
||||
import org.springframework.data.jpa.util.ClassPathExclusions;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link QueryEnhancerFactory}.
|
||||
@@ -43,7 +34,7 @@ class QueryEnhancerFactoryUnitTests {
|
||||
|
||||
StringQuery query = new StringQuery("select new com.example.User(u.firstname) from User u", false);
|
||||
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(query);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(query).create(query);
|
||||
|
||||
assertThat(queryEnhancer) //
|
||||
.isInstanceOf(JpaQueryEnhancer.class);
|
||||
@@ -58,79 +49,10 @@ class QueryEnhancerFactoryUnitTests {
|
||||
|
||||
StringQuery query = new StringQuery("select * from User", true);
|
||||
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(query);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(query).create(query);
|
||||
|
||||
assertThat(queryEnhancer) //
|
||||
.isInstanceOf(JSqlParserQueryEnhancer.class);
|
||||
}
|
||||
|
||||
@ParameterizedTest // GH-2989
|
||||
@MethodSource("nativeEnhancerSelectionArgs")
|
||||
void createsNativeImplementationAccordingToUserChoice(@Nullable String selection, NativeQueryEnhancer enhancer) {
|
||||
|
||||
assertThat(NativeQueryEnhancer.JSQLPARSER_PRESENT).isTrue();
|
||||
|
||||
withSystemProperty(NativeQueryEnhancer.NATIVE_PARSER_PROPERTY, selection, () -> {
|
||||
assertThat(NativeQueryEnhancer.select()).isEqualTo(enhancer);
|
||||
});
|
||||
}
|
||||
|
||||
static Stream<Arguments> nativeEnhancerSelectionArgs() {
|
||||
return Stream.of(Arguments.of(null, NativeQueryEnhancer.JSQLPARSER), //
|
||||
Arguments.of("", NativeQueryEnhancer.JSQLPARSER), //
|
||||
Arguments.of("auto", NativeQueryEnhancer.JSQLPARSER), //
|
||||
Arguments.of("regex", NativeQueryEnhancer.REGEX), //
|
||||
Arguments.of("jsqlparser", NativeQueryEnhancer.JSQLPARSER));
|
||||
}
|
||||
|
||||
@ParameterizedTest // GH-2989
|
||||
@MethodSource("nativeEnhancerExclusionSelectionArgs")
|
||||
@ClassPathExclusions(packages = { "net.sf.jsqlparser.parser" })
|
||||
void createsNativeImplementationAccordingWithoutJsqlParserToUserChoice(@Nullable String selection,
|
||||
NativeQueryEnhancer enhancer) {
|
||||
|
||||
assertThat(NativeQueryEnhancer.JSQLPARSER_PRESENT).isFalse();
|
||||
|
||||
withSystemProperty(NativeQueryEnhancer.NATIVE_PARSER_PROPERTY, selection, () -> {
|
||||
assertThat(NativeQueryEnhancer.select()).isEqualTo(enhancer);
|
||||
});
|
||||
}
|
||||
|
||||
static Stream<Arguments> nativeEnhancerExclusionSelectionArgs() {
|
||||
return Stream.of(Arguments.of(null, NativeQueryEnhancer.REGEX), //
|
||||
Arguments.of("", NativeQueryEnhancer.REGEX), //
|
||||
Arguments.of("auto", NativeQueryEnhancer.REGEX), //
|
||||
Arguments.of("regex", NativeQueryEnhancer.REGEX), //
|
||||
Arguments.of("jsqlparser", NativeQueryEnhancer.JSQLPARSER));
|
||||
}
|
||||
|
||||
@Test // GH-2989
|
||||
@ClassPathExclusions(packages = { "net.sf.jsqlparser.parser" })
|
||||
void selectedDefaultImplementationIfJsqlNotAvailable() {
|
||||
|
||||
assertThat(NativeQueryEnhancer.JSQLPARSER_PRESENT).isFalse();
|
||||
assertThat(NativeQueryEnhancer.select()).isEqualTo(NativeQueryEnhancer.REGEX);
|
||||
}
|
||||
|
||||
void withSystemProperty(String property, @Nullable String value, Runnable exeution) {
|
||||
|
||||
String currentValue = System.getProperty(property);
|
||||
if (value != null) {
|
||||
System.setProperty(property, value);
|
||||
} else {
|
||||
System.clearProperty(property);
|
||||
}
|
||||
try {
|
||||
exeution.run();
|
||||
} finally {
|
||||
if (currentValue != null) {
|
||||
System.setProperty(property, currentValue);
|
||||
} else {
|
||||
System.clearProperty(property);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -35,8 +35,7 @@ abstract class QueryEnhancerTckTests {
|
||||
@MethodSource("nativeCountQueries") // GH-2773
|
||||
void shouldDeriveNativeCountQuery(String query, String expected) {
|
||||
|
||||
DeclaredQuery declaredQuery = DeclaredQuery.of(query, true);
|
||||
QueryEnhancer enhancer = createQueryEnhancer(declaredQuery);
|
||||
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.ofNative(query));
|
||||
String countQueryFor = enhancer.createCountQueryFor();
|
||||
|
||||
// lenient cleanup to allow for rendering variance
|
||||
@@ -120,8 +119,7 @@ abstract class QueryEnhancerTckTests {
|
||||
@MethodSource("jpqlCountQueries")
|
||||
void shouldDeriveJpqlCountQuery(String query, String expected) {
|
||||
|
||||
DeclaredQuery declaredQuery = DeclaredQuery.of(query, false);
|
||||
QueryEnhancer enhancer = createQueryEnhancer(declaredQuery);
|
||||
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.ofJpql(query));
|
||||
String countQueryFor = enhancer.createCountQueryFor(null);
|
||||
|
||||
assertThat(countQueryFor).isEqualToIgnoringCase(expected);
|
||||
@@ -180,8 +178,7 @@ abstract class QueryEnhancerTckTests {
|
||||
@MethodSource("nativeQueriesWithVariables")
|
||||
void shouldDeriveNativeCountQueryWithVariable(String query, String expected) {
|
||||
|
||||
DeclaredQuery declaredQuery = DeclaredQuery.of(query, true);
|
||||
QueryEnhancer enhancer = createQueryEnhancer(declaredQuery);
|
||||
QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.ofNative(query));
|
||||
String countQueryFor = enhancer.createCountQueryFor();
|
||||
|
||||
assertThat(countQueryFor).isEqualToIgnoringCase(expected);
|
||||
@@ -211,6 +208,6 @@ abstract class QueryEnhancerTckTests {
|
||||
assertThat(createQueryEnhancer(query).getProjection()).isEqualTo("x, frommage, y");
|
||||
}
|
||||
|
||||
abstract QueryEnhancer createQueryEnhancer(DeclaredQuery declaredQuery);
|
||||
abstract QueryEnhancer createQueryEnhancer(DeclaredQuery query);
|
||||
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ class QueryEnhancerUnitTests {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("detectsAliasWithUCorrectlySource")
|
||||
void detectsAliasWithUCorrectly(DeclaredQuery query, String alias) {
|
||||
void detectsAliasWithUCorrectly(IntrospectedQuery query, String alias) {
|
||||
|
||||
assumeThat(query.getQueryString()).as("JsqlParser does not support simple JPA syntax")
|
||||
.doesNotStartWithIgnoringCase("from");
|
||||
@@ -186,8 +186,7 @@ class QueryEnhancerUnitTests {
|
||||
true);
|
||||
|
||||
assertThat(getEnhancer(query).applySorting(Sort.by("name"), "p")) //
|
||||
.startsWithIgnoringCase(query.getQueryString())
|
||||
.endsWithIgnoringCase("ORDER BY p.name ASC");
|
||||
.startsWithIgnoringCase(query.getQueryString()).endsWithIgnoringCase("ORDER BY p.name ASC");
|
||||
}
|
||||
|
||||
@Test // GH-2812
|
||||
@@ -433,7 +432,7 @@ class QueryEnhancerUnitTests {
|
||||
|
||||
assertThat(
|
||||
QueryUtils.getFunctionAliases("select new MyDto(sum(case when myEntity.prop3=0 then 1 else 0 end) as myAlias")) //
|
||||
.contains("myAlias");
|
||||
.contains("myAlias");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1506
|
||||
@@ -538,7 +537,7 @@ class QueryEnhancerUnitTests {
|
||||
|
||||
@ParameterizedTest // DATAJPA-1679
|
||||
@MethodSource("findProjectionClauseWithDistinctSource")
|
||||
void findProjectionClauseWithDistinct(DeclaredQuery query, String expected) {
|
||||
void findProjectionClauseWithDistinct(IntrospectedQuery query, String expected) {
|
||||
|
||||
SoftAssertions.assertSoftly(sofly -> sofly.assertThat(getEnhancer(query).getProjection()).isEqualTo(expected));
|
||||
}
|
||||
@@ -633,7 +632,8 @@ class QueryEnhancerUnitTests {
|
||||
assertThat(modiQuery.hasConstructorExpression()).isEqualTo(constructorExpressionNotConsideringQueryType);
|
||||
|
||||
assertThat(countQueryForNotConsiderQueryType).isEqualToIgnoringCase(modifyingQuery);
|
||||
assertThat(QueryEnhancerFactory.forQuery(modiQuery).createCountQueryFor()).isEqualToIgnoringCase(modifyingQuery);
|
||||
assertThat(QueryEnhancerFactory.forQuery(modiQuery).create(modiQuery).createCountQueryFor())
|
||||
.isEqualToIgnoringCase(modifyingQuery);
|
||||
}
|
||||
|
||||
@ParameterizedTest // GH-2593
|
||||
@@ -641,7 +641,7 @@ class QueryEnhancerUnitTests {
|
||||
void insertStatementIsProcessedSameAsDefault(String insertQuery) {
|
||||
|
||||
StringQuery stringQuery = new StringQuery(insertQuery, true);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery);
|
||||
QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery).create(stringQuery);
|
||||
|
||||
Sort sorting = Sort.by("day").descending();
|
||||
|
||||
@@ -696,8 +696,8 @@ class QueryEnhancerUnitTests {
|
||||
assertThat(getEnhancer(originalQuery).createCountQueryFor()).isEqualToIgnoringCase(countQuery);
|
||||
}
|
||||
|
||||
private static QueryEnhancer getEnhancer(DeclaredQuery query) {
|
||||
return QueryEnhancerFactory.forQuery(query);
|
||||
private static QueryEnhancer getEnhancer(IntrospectedQuery query) {
|
||||
return QueryEnhancerFactory.forQuery(query).create(query);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -52,7 +52,8 @@ class QueryParameterSetterFactoryUnitTests {
|
||||
|
||||
@Test // DATAJPA-1058
|
||||
void noExceptionWhenQueryDoesNotContainNamedParameters() {
|
||||
setterFactory.create(binding);
|
||||
setterFactory.create(binding,
|
||||
EntityQuery.introspectJpql("from Employee e", QueryEnhancerSelector.DEFAULT_SELECTOR));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1058
|
||||
@@ -61,28 +62,14 @@ class QueryParameterSetterFactoryUnitTests {
|
||||
when(binding.getOrigin()).thenReturn(ParameterOrigin.ofParameter("NamedParameter", 1));
|
||||
|
||||
assertThatExceptionOfType(IllegalStateException.class) //
|
||||
.isThrownBy(() -> setterFactory.create(binding
|
||||
)) //
|
||||
.isThrownBy(() -> setterFactory.create(binding,
|
||||
EntityQuery.introspectJpql("from Employee e where e.name = :NamedParameter",
|
||||
QueryEnhancerSelector.DEFAULT_SELECTOR))) //
|
||||
.withMessageContaining("Java 8") //
|
||||
.withMessageContaining("@Param") //
|
||||
.withMessageContaining("-parameters");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1281
|
||||
void exceptionWhenCriteriaQueryContainsInsufficientAmountOfParameters() {
|
||||
|
||||
// no parameter present in the criteria query
|
||||
QueryParameterSetterFactory setterFactory = QueryParameterSetterFactory.forPartTreeQuery(parameters);
|
||||
|
||||
// one argument present in the method signature
|
||||
when(binding.getRequiredPosition()).thenReturn(1);
|
||||
when(binding.getOrigin()).thenReturn(ParameterOrigin.ofParameter(null, 1));
|
||||
|
||||
assertThatExceptionOfType(IllegalArgumentException.class) //
|
||||
.isThrownBy(() -> setterFactory.create(binding)) //
|
||||
.withMessage("At least 1 parameter(s) provided but only 0 parameter(s) present in query");
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1281
|
||||
void exceptionWhenBasicQueryContainsInsufficientAmountOfParameters() {
|
||||
|
||||
@@ -94,7 +81,10 @@ class QueryParameterSetterFactoryUnitTests {
|
||||
when(binding.getOrigin()).thenReturn(ParameterOrigin.ofParameter(null, 1));
|
||||
|
||||
assertThatExceptionOfType(IllegalArgumentException.class) //
|
||||
.isThrownBy(() -> setterFactory.create(binding)) //
|
||||
.isThrownBy(
|
||||
() -> setterFactory.create(binding,
|
||||
EntityQuery.introspectJpql("from Employee e where e.name = ?1",
|
||||
QueryEnhancerSelector.DEFAULT_SELECTOR))) //
|
||||
.withMessage("At least 1 parameter(s) provided but only 0 parameter(s) present in query");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ import org.springframework.data.jpa.domain.sample.User;
|
||||
import org.springframework.data.jpa.provider.QueryExtractor;
|
||||
import org.springframework.data.jpa.repository.NativeQuery;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.jpa.repository.QueryRewriter;
|
||||
import org.springframework.data.jpa.repository.sample.UserRepository;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
@@ -75,6 +74,9 @@ import org.springframework.data.util.TypeInformation;
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
class SimpleJpaQueryUnitTests {
|
||||
|
||||
private static final JpaQueryConfiguration CONFIG = new JpaQueryConfiguration(QueryRewriterProvider.simple(),
|
||||
QueryEnhancerSelector.DEFAULT_SELECTOR, ValueExpressionDelegate.create(), EscapeCharacter.DEFAULT);
|
||||
|
||||
private static final String USER_QUERY = "select u from User u";
|
||||
|
||||
private JpaQueryMethod method;
|
||||
@@ -119,8 +121,7 @@ class SimpleJpaQueryUnitTests {
|
||||
extractor);
|
||||
when(em.createQuery("foo", Long.class)).thenReturn(typedQuery);
|
||||
|
||||
SimpleJpaQuery jpaQuery = new SimpleJpaQuery(method, em, "select u from User u", null,
|
||||
QueryRewriter.IdentityQueryRewriter.INSTANCE, ValueExpressionDelegate.create());
|
||||
SimpleJpaQuery jpaQuery = new SimpleJpaQuery(method, em, "select u from User u", null, CONFIG);
|
||||
|
||||
assertThat(jpaQuery.createCountQuery(new JpaParametersParameterAccessor(method.getParameters(), new Object[] {})))
|
||||
.isEqualTo(typedQuery);
|
||||
@@ -134,8 +135,7 @@ class SimpleJpaQueryUnitTests {
|
||||
Method method = UserRepository.class.getMethod("findAllPaged", Pageable.class);
|
||||
JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, factory, extractor);
|
||||
|
||||
AbstractJpaQuery jpaQuery = new SimpleJpaQuery(queryMethod, em, "select u from User u", null,
|
||||
QueryRewriter.IdentityQueryRewriter.INSTANCE, ValueExpressionDelegate.create());
|
||||
AbstractJpaQuery jpaQuery = new SimpleJpaQuery(queryMethod, em, "select u from User u", null, CONFIG);
|
||||
jpaQuery.createCountQuery(
|
||||
new JpaParametersParameterAccessor(queryMethod.getParameters(), new Object[] { PageRequest.of(1, 10) }));
|
||||
|
||||
@@ -149,9 +149,8 @@ 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, QueryRewriter.IdentityQueryRewriter.INSTANCE,
|
||||
ValueExpressionDelegate.create());
|
||||
AbstractJpaQuery jpaQuery = JpaQueryLookupStrategy.DeclaredQueryLookupStrategy.createStringQuery(queryMethod, em,
|
||||
queryMethod.getAnnotatedQuery(), null, CONFIG);
|
||||
|
||||
assertThat(jpaQuery).isInstanceOf(NativeJpaQuery.class);
|
||||
|
||||
@@ -169,9 +168,8 @@ class SimpleJpaQueryUnitTests {
|
||||
|
||||
Method method = SampleRepository.class.getMethod("findByLastnameNativeAnnotation", String.class);
|
||||
JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, factory, extractor);
|
||||
AbstractJpaQuery jpaQuery = JpaQueryFactory.INSTANCE.fromMethodWithQueryString(queryMethod, em,
|
||||
queryMethod.getAnnotatedQuery(), null, QueryRewriter.IdentityQueryRewriter.INSTANCE,
|
||||
ValueExpressionDelegate.create());
|
||||
AbstractJpaQuery jpaQuery = JpaQueryLookupStrategy.DeclaredQueryLookupStrategy.createStringQuery(queryMethod, em,
|
||||
queryMethod.getAnnotatedQuery(), null, CONFIG);
|
||||
|
||||
assertThat(jpaQuery).isInstanceOf(NativeJpaQuery.class);
|
||||
|
||||
@@ -239,10 +237,11 @@ class SimpleJpaQueryUnitTests {
|
||||
when(em.createNativeQuery(anyString())).thenReturn(query);
|
||||
|
||||
AbstractJpaQuery jpaQuery = createJpaQuery(
|
||||
SampleRepository.class.getMethod("findAllWithBindingsOnlyInCountQuery", String.class, Pageable.class), Optional.empty());
|
||||
SampleRepository.class.getMethod("findAllWithBindingsOnlyInCountQuery", String.class, Pageable.class),
|
||||
Optional.empty());
|
||||
|
||||
jpaQuery.doCreateCountQuery(new JpaParametersParameterAccessor(jpaQuery.getQueryMethod().getParameters(),
|
||||
new Object[]{"data", PageRequest.of(0, 10)}));
|
||||
new Object[] { "data", PageRequest.of(0, 10) }));
|
||||
|
||||
ArgumentCaptor<String> queryStringCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(em).createQuery(queryStringCaptor.capture(), eq(Long.class));
|
||||
@@ -283,8 +282,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", QueryRewriter.IdentityQueryRewriter.INSTANCE,
|
||||
ValueExpressionDelegate.create());
|
||||
"select count(u.id) from #{#entityName} u", CONFIG);
|
||||
jpaQuery.createCountQuery(
|
||||
new JpaParametersParameterAccessor(queryMethod.getParameters(), new Object[] { PageRequest.of(1, 10) }));
|
||||
|
||||
@@ -296,16 +294,18 @@ class SimpleJpaQueryUnitTests {
|
||||
return createJpaQuery(method, null);
|
||||
}
|
||||
|
||||
private AbstractJpaQuery createJpaQuery(JpaQueryMethod queryMethod, @Nullable String queryString, @Nullable String countQueryString) {
|
||||
private AbstractJpaQuery createJpaQuery(JpaQueryMethod queryMethod, @Nullable String queryString,
|
||||
@Nullable String countQueryString) {
|
||||
|
||||
return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(queryMethod, em, queryString, countQueryString,
|
||||
QueryRewriter.IdentityQueryRewriter.INSTANCE, ValueExpressionDelegate.create());
|
||||
return JpaQueryLookupStrategy.DeclaredQueryLookupStrategy.createStringQuery(queryMethod, em, queryString,
|
||||
countQueryString, CONFIG);
|
||||
}
|
||||
|
||||
private AbstractJpaQuery createJpaQuery(Method method, @Nullable Optional<String> countQueryString) {
|
||||
|
||||
JpaQueryMethod queryMethod = new JpaQueryMethod(method, metadata, factory, extractor);
|
||||
return createJpaQuery(queryMethod, queryMethod.getAnnotatedQuery(), countQueryString == null ? null : countQueryString.orElse(queryMethod.getCountQuery()));
|
||||
return createJpaQuery(queryMethod, queryMethod.getAnnotatedQuery(),
|
||||
countQueryString == null ? null : countQueryString.orElse(queryMethod.getCountQuery()));
|
||||
}
|
||||
|
||||
interface SampleRepository {
|
||||
@@ -337,8 +337,8 @@ class SimpleJpaQueryUnitTests {
|
||||
@Query(value = "select u from #{#entityName} u", countQuery = "select count(u.id) from #{#entityName} u")
|
||||
List<User> findAllWithExpressionInCountQuery(Pageable pageable);
|
||||
|
||||
|
||||
@Query(value = "select u from User u", countQuery = "select count(u.id) from #{#entityName} u where u.name = :#{#arg0}")
|
||||
@Query(value = "select u from User u",
|
||||
countQuery = "select count(u.id) from #{#entityName} u where u.name = :#{#arg0}")
|
||||
List<User> findAllWithBindingsOnlyInCountQuery(String arg0, Pageable pageable);
|
||||
|
||||
// Typo in named parameter
|
||||
|
||||
@@ -912,12 +912,14 @@ class StringQueryUnitTests {
|
||||
|
||||
void checkNumberOfNamedParameters(String query, int expectedSize, String label, boolean nativeQuery) {
|
||||
|
||||
DeclaredQuery declaredQuery = DeclaredQuery.of(query, nativeQuery);
|
||||
EntityQuery introspectedQuery = nativeQuery
|
||||
? EntityQuery.introspectNativeQuery(query, QueryEnhancerSelector.DEFAULT_SELECTOR)
|
||||
: EntityQuery.introspectJpql(query, QueryEnhancerSelector.DEFAULT_SELECTOR);
|
||||
|
||||
assertThat(declaredQuery.hasNamedParameter()) //
|
||||
assertThat(introspectedQuery.hasNamedParameter()) //
|
||||
.describedAs("hasNamed Parameter " + label) //
|
||||
.isEqualTo(expectedSize > 0);
|
||||
assertThat(declaredQuery.getParameterBindings()) //
|
||||
assertThat(introspectedQuery.getParameterBindings()) //
|
||||
.describedAs("parameterBindings " + label) //
|
||||
.hasSize(expectedSize);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user