diff --git a/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/HqlParserBenchmarks.java b/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/HqlParserBenchmarks.java index fb524d76b..ecbb4eb23 100644 --- a/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/HqlParserBenchmarks.java +++ b/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/HqlParserBenchmarks.java @@ -55,7 +55,7 @@ public class HqlParserBenchmarks { OR p.description LIKE "cost overrun" """; - query = DeclaredQuery.ofJpql(s); + query = DeclaredQuery.jpqlQuery(s); enhancer = QueryEnhancerFactory.forQuery(query).create(query); } } diff --git a/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerBenchmarks.java b/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerBenchmarks.java index aeb1764c5..a5c9cdce2 100644 --- a/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerBenchmarks.java +++ b/spring-data-jpa/src/jmh/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerBenchmarks.java @@ -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.ofNative(s)); + enhancer = new JSqlParserQueryEnhancer(DeclaredQuery.nativeQuery(s)); } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/BindableQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/BindableQuery.java new file mode 100644 index 000000000..66e95a93c --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/BindableQuery.java @@ -0,0 +1,67 @@ +/* + * Copyright 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 java.util.Collections; +import java.util.List; + + +/** + * @author Christoph Strobl + */ +final class BindableQuery implements DeclaredQuery { + + private final DeclaredQuery source; + private final String bindableQueryString; + private final List bindings; + private final boolean usesJdbcStyleParameters; + + public BindableQuery(DeclaredQuery source, String bindableQueryString, List bindings, boolean usesJdbcStyleParameters) { + this.source = source; + this.bindableQueryString = bindableQueryString; + this.bindings = bindings; + this.usesJdbcStyleParameters = usesJdbcStyleParameters; + } + + @Override + public boolean isNativeQuery() { + return source.isNativeQuery(); + } + + boolean hasBindings() { + return !bindings.isEmpty(); + } + + boolean usesJdbcStyleParameters() { + return usesJdbcStyleParameters; + } + + @Override + public String getQueryString() { + return bindableQueryString; + } + + public BindableQuery unifyBindings(BindableQuery comparisonQuery) { + if (comparisonQuery.hasBindings() && !comparisonQuery.bindings.equals(this.bindings)) { + return new BindableQuery(source, bindableQueryString, comparisonQuery.bindings, usesJdbcStyleParameters); + } + return this; + } + + public List getBindings() { + return Collections.unmodifiableList(bindings); + } +} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java index ca32d1f46..152c40c38 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java @@ -23,33 +23,28 @@ package org.springframework.data.jpa.repository.query; * @author Mark Paluch * @since 2.0.3 */ -public interface DeclaredQuery { +public interface DeclaredQuery extends StructuredQuery { /** * Creates a DeclaredQuery for a JPQL query. * - * @param query the JPQL query string. - * @return + * @param jpql the JPQL query string. + * @return new instance of {@link DeclaredQuery}. */ - static DeclaredQuery ofJpql(String query) { - return new DefaultDeclaredQuery(query, false); + static DeclaredQuery jpqlQuery(String jpql) { + return new JpqlQuery(jpql); } /** * Creates a DeclaredQuery for a native query. * - * @param query the native query string. - * @return + * @param sql the native query string. + * @return new instance of {@link DeclaredQuery}. */ - static DeclaredQuery ofNative(String query) { - return new DefaultDeclaredQuery(query, true); + static DeclaredQuery nativeQuery(String sql) { + return new NativeQuery(sql); } - /** - * Returns the query string. - */ - String getQueryString(); - /** * Return whether the query is a native query of not. * diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultDeclaredQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultDeclaredQuery.java deleted file mode 100644 index a24512a99..000000000 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultDeclaredQuery.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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(); - } -} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java index 1fe623662..3d4aba285 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java @@ -29,13 +29,13 @@ import org.jspecify.annotations.Nullable; */ public class DefaultQueryEnhancer implements QueryEnhancer { - private final DeclaredQuery query; + private final StructuredQuery query; private final boolean hasConstructorExpression; private final @Nullable String alias; private final String projection; private final Set joinAliases; - public DefaultQueryEnhancer(DeclaredQuery query) { + public DefaultQueryEnhancer(StructuredQuery query) { this.query = query; this.hasConstructorExpression = QueryUtils.hasConstructorExpression(query.getQueryString()); this.alias = QueryUtils.detectAlias(query.getQueryString()); @@ -60,7 +60,9 @@ public class DefaultQueryEnhancer implements QueryEnhancer { @Override public String createCountQueryFor(@Nullable String countProjection) { - return QueryUtils.createCountQueryFor(this.query.getQueryString(), countProjection, this.query.isNativeQuery()); + + boolean nativeQuery = this.query instanceof DeclaredQuery dc ? dc.isNativeQuery() : true; + return QueryUtils.createCountQueryFor(this.query.getQueryString(), countProjection, nativeQuery); } @Override @@ -84,7 +86,7 @@ public class DefaultQueryEnhancer implements QueryEnhancer { } @Override - public DeclaredQuery getQuery() { + public StructuredQuery getQuery() { return this.query; } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyIntrospectedQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyIntrospectedQuery.java index c51f0c4ca..ec92ee81c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyIntrospectedQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyIntrospectedQuery.java @@ -63,6 +63,11 @@ class EmptyIntrospectedQuery implements EntityQuery { return false; } + @Override + public String getQueryString() { + return ""; + } + @Override public List getParameterBindings() { return Collections.emptyList(); @@ -82,4 +87,9 @@ class EmptyIntrospectedQuery implements EntityQuery { public boolean usesJdbcStyleParameters() { return false; } + + @Override + public DeclaredQuery getDeclaredQuery() { + return DeclaredQuery.nativeQuery(""); + } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/IntrospectedQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/IntrospectedQuery.java index 427dbcc03..4a29bce6c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/IntrospectedQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/IntrospectedQuery.java @@ -24,7 +24,13 @@ import java.util.List; * @author Diego Krupitza * @since 2.0.3 */ -interface IntrospectedQuery extends DeclaredQuery { +interface IntrospectedQuery extends StructuredQuery { + + DeclaredQuery getDeclaredQuery(); + + default String getQueryString() { + return getDeclaredQuery().getQueryString(); + } /** * @return whether the underlying query has at least one named parameter. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java index 141d61b5f..d1fab1032 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java @@ -74,7 +74,7 @@ import org.springframework.util.StringUtils; */ public class JSqlParserQueryEnhancer implements QueryEnhancer { - private final DeclaredQuery query; + private final StructuredQuery query; private final Statement statement; private final ParsedType parsedType; private final boolean hasConstructorExpression; @@ -87,7 +87,7 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer { /** * @param query the query we want to enhance. Must not be {@literal null}. */ - public JSqlParserQueryEnhancer(DeclaredQuery query) { + public JSqlParserQueryEnhancer(StructuredQuery query) { this.query = query; this.statement = parseStatement(query.getQueryString(), Statement.class); @@ -339,7 +339,7 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer { } @Override - public DeclaredQuery getQuery() { + public StructuredQuery getQuery() { return this.query; } @@ -410,8 +410,8 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer { this.query::getQueryString); } - private static String createCountQueryFor(PlainSelect selectBody, @Nullable String countProjection, - @Nullable String primaryAlias) { + private static String createCountQueryFor(StructuredQuery query, PlainSelect selectBody, + @Nullable String countProjection, @Nullable String primaryAlias) { // remove order by selectBody.setOrderByElements(null); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQuery.java new file mode 100644 index 000000000..6a8f3cce0 --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQuery.java @@ -0,0 +1,38 @@ +/* + * Copyright 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; + +/** + * @author Christoph Strobl + */ +final class JpqlQuery implements DeclaredQuery { + + private final String jpql; + + JpqlQuery(String jpql) { + this.jpql = jpql; + } + + @Override + public boolean isNativeQuery() { + return false; + } + + @Override + public String getQueryString() { + return jpql; + } +} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java index 6f4138760..b81820c6f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java @@ -185,16 +185,11 @@ final class NamedQuery extends AbstractJpaQuery { EntityManager em = getEntityManager(); TypedQuery countQuery; - String cacheKey; if (namedCountQueryIsPresent) { - cacheKey = countQueryName; countQuery = em.createNamedQuery(countQueryName, Long.class); - } else { String countQueryString = entityQuery.get().deriveCountQuery(countProjection).getQueryString(); - countQueryString = potentiallyRewriteQuery(countQueryString, accessor.getSort(), accessor.getPageable()); - cacheKey = countQueryString; countQuery = em.createQuery(countQueryString, Long.class); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeQuery.java new file mode 100644 index 000000000..6ba9f81ba --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeQuery.java @@ -0,0 +1,38 @@ +/* + * Copyright 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; + +/** + * @author Christoph Strobl + */ +final class NativeQuery implements DeclaredQuery { + + private final String sql; + + NativeQuery(String sql) { + this.sql = sql; + } + + @Override + public boolean isNativeQuery() { + return true; + } + + @Override + public String getQueryString() { + return sql; + } +} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java index 8abf7d461..fc34606f4 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinderFactory.java @@ -92,7 +92,6 @@ class ParameterBinderFactory { Assert.notNull(parser, "SpelExpressionParser must not be null"); Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null"); - List bindings = query.getParameterBindings(); QueryParameterSetterFactory expressionSetterFactory = QueryParameterSetterFactory.parsing(parser, evaluationContextProvider); @@ -101,7 +100,8 @@ class ParameterBinderFactory { boolean usesPaging = query instanceof EntityQuery eq && eq.usesPaging(); - return new ParameterBinder(parameters, createSetters(bindings, query, expressionSetterFactory, basicSetterFactory), + // TODO: lets maybe obtain the bindable query and pass that on to create the setters? + return new ParameterBinder(parameters, createSetters(query.getParameterBindings(), query, expressionSetterFactory, basicSetterFactory), !usesPaging); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancer.java index ff9f44c44..528426f82 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancer.java @@ -66,7 +66,7 @@ public interface QueryEnhancer { * * @return non-null {@link DeclaredQuery} that wraps the query. */ - DeclaredQuery getQuery(); + StructuredQuery getQuery(); /** * Adds {@literal order by} clause to the JPQL query. Uses the first alias to bind the sorting property to. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactories.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactories.java index b88a6953f..07ed8642c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactories.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactories.java @@ -57,7 +57,7 @@ public class QueryEnhancerFactories { } @Override - public QueryEnhancer create(DeclaredQuery query) { + public QueryEnhancer create(StructuredQuery query) { return new DefaultQueryEnhancer(query); } }, @@ -69,7 +69,7 @@ public class QueryEnhancerFactories { } @Override - public QueryEnhancer create(DeclaredQuery query) { + public QueryEnhancer create(StructuredQuery query) { if (jSqlParserPresent) { return new JSqlParserQueryEnhancer(query); } @@ -85,7 +85,7 @@ public class QueryEnhancerFactories { } @Override - public QueryEnhancer create(DeclaredQuery query) { + public QueryEnhancer create(StructuredQuery query) { return JpaQueryEnhancer.forHql(query.getQueryString()); } }, @@ -96,7 +96,7 @@ public class QueryEnhancerFactories { } @Override - public QueryEnhancer create(DeclaredQuery query) { + public QueryEnhancer create(StructuredQuery query) { return JpaQueryEnhancer.forEql(query.getQueryString()); } }, @@ -107,7 +107,7 @@ public class QueryEnhancerFactories { } @Override - public QueryEnhancer create(DeclaredQuery query) { + public QueryEnhancer create(StructuredQuery query) { return JpaQueryEnhancer.forJpql(query.getQueryString()); } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactory.java index a3e7b5f06..26bdf4b5b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactory.java @@ -40,7 +40,7 @@ public interface QueryEnhancerFactory { * @param query the query to be enhanced and introspected. * @return */ - QueryEnhancer create(DeclaredQuery query); + QueryEnhancer create(StructuredQuery query); /** * Creates a new {@link QueryEnhancerFactory} for the given {@link DeclaredQuery}. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerSelector.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerSelector.java index 75bee83f1..93268c638 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerSelector.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancerSelector.java @@ -66,7 +66,7 @@ public interface QueryEnhancerSelector { private final QueryEnhancerFactory nativeQuery; private final QueryEnhancerFactory jpql; - public DefaultQueryEnhancerSelector() { + DefaultQueryEnhancerSelector() { this(DEFAULT_NATIVE, DEFAULT_JPQL); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java index 39af6fb1e..e15bcb2e3 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java @@ -64,11 +64,8 @@ import org.springframework.util.StringUtils; */ class StringQuery implements EntityQuery { - private final String query; - private final List bindings; + private final BindableQuery bindableQuery; private final boolean containsPageableInSpel; - private final boolean usesJdbcStyleParameters; - private final boolean isNative; private final QueryEnhancerFactory queryEnhancerFactory; private final QueryEnhancer queryEnhancer; private final boolean hasNamedParameters; @@ -78,7 +75,7 @@ class StringQuery implements EntityQuery { * * @param query must not be {@literal null} or empty. */ - public StringQuery(String query, boolean isNative) { + StringQuery(String query, boolean isNative) { this(query, isNative, QueryEnhancerSelector.DEFAULT_SELECTOR, it -> {}); } @@ -91,29 +88,15 @@ class StringQuery implements EntityQuery { 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); + DeclaredQuery source = isNative ? DeclaredQuery.nativeQuery(query) : DeclaredQuery.jpqlQuery(query); + this.bindableQuery = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(source); - this.usesJdbcStyleParameters = queryMeta.usesJdbcStyleParameters; - this.queryEnhancer = factory.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; + parameterPostProcessor.accept(this.bindableQuery.getBindings()); + this.queryEnhancer = factory.create(this.bindableQuery); + this.hasNamedParameters = containsNamedParameter(this.bindableQuery.getBindings()); } /** @@ -125,29 +108,32 @@ class StringQuery implements EntityQuery { Assert.hasText(query, "Query must not be null or empty"); - this.isNative = isNative; - this.bindings = new ArrayList<>(); this.containsPageableInSpel = query.contains("#pageable"); + DeclaredQuery source = isNative ? DeclaredQuery.nativeQuery(query) : DeclaredQuery.jpqlQuery(query); - Metadata queryMeta = new Metadata(); - this.query = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query, - this.bindings, queryMeta); + this.bindableQuery = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(source); - 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.queryEnhancerFactory = selector.select(source); + this.queryEnhancer = queryEnhancerFactory.create(this.bindableQuery); + parameterPostProcessor.accept(this.bindableQuery.getBindings()); + this.hasNamedParameters = containsNamedParameter(this.bindableQuery.getBindings()); + } + /** + * internal copy constructor + * + * @param bindableQuery + * @param factory + * @param enhancer + * @param hasNamedParameters + * @param containsPageableInSpel + */ + private StringQuery(BindableQuery bindableQuery, QueryEnhancerFactory factory, QueryEnhancer enhancer, boolean hasNamedParameters, boolean containsPageableInSpel) { + this.bindableQuery = bindableQuery; + this.queryEnhancerFactory = factory; + this.queryEnhancer = enhancer; this.hasNamedParameters = hasNamedParameters; + this.containsPageableInSpel = containsPageableInSpel; } QueryEnhancer getQueryEnhancer() { @@ -158,16 +144,21 @@ class StringQuery implements EntityQuery { * Returns whether we have found some like bindings. */ boolean hasParameterBindings() { - return !bindings.isEmpty(); + return this.bindableQuery.hasBindings(); } String getProjection() { return this.queryEnhancer.getProjection(); } + @Override + public String getQueryString() { + return bindableQuery.getQueryString(); + } + @Override public List getParameterBindings() { - return bindings; + return this.bindableQuery.getBindings(); } @Override @@ -177,14 +168,14 @@ class StringQuery implements EntityQuery { // JPA parameter markers and not the original expressions anymore. return new StringQuery(this.queryEnhancer.createCountQueryFor(countQueryProjection), // - this.isNative, queryEnhancerFactory, derivedBindings -> { + this.bindableQuery.isNativeQuery(), queryEnhancerFactory, derivedBindings -> { // 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. if (this.hasParameterBindings() && !this.getParameterBindings().equals(derivedBindings)) { - for (ParameterBinding binding : bindings) { + for (ParameterBinding binding : getParameterBindings()) { Predicate identifier = binding::bindsTo; Predicate notCompatible = Predicate.not(binding::isCompatibleWith); @@ -206,12 +197,7 @@ class StringQuery implements EntityQuery { @Override public boolean usesJdbcStyleParameters() { - return usesJdbcStyleParameters; - } - - @Override - public String getQueryString() { - return query; + return bindableQuery.usesJdbcStyleParameters(); } public @Nullable String getAlias() { @@ -239,8 +225,17 @@ class StringQuery implements EntityQuery { } @Override - public boolean isNativeQuery() { - return isNative; + public DeclaredQuery getDeclaredQuery() { + return bindableQuery; + } + + private static boolean containsNamedParameter(List bindings) { + for (ParameterBinding parameterBinding : bindings) { + if (parameterBinding.getIdentifier().hasName() && parameterBinding.getOrigin().isMethodArgument()) { + return true; + } + } + return false; } /** @@ -376,8 +371,7 @@ class StringQuery implements EntityQuery { * Parses {@link ParameterBinding} instances from the given query and adds them to the registered bindings. Returns * the cleaned up query. */ - String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String query, List bindings, - Metadata queryMeta) { + BindableQuery parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(DeclaredQuery query) { IndexedParameterLabels parameterLabels = new IndexedParameterLabels(findParameterIndices(query)); boolean parametersShouldBeAccessedByIndex = parameterLabels.hasLabels(); @@ -385,11 +379,11 @@ class StringQuery implements EntityQuery { /* * Prefer indexed access over named parameters if only SpEL Expression parameters are present. */ - if (!parametersShouldBeAccessedByIndex && query.contains("?#{")) { + if (!parametersShouldBeAccessedByIndex && query.getQueryString().contains("?#{")) { parametersShouldBeAccessedByIndex = true; } - ValueExpressionQueryRewriter.ParsedQuery parsedQuery = createSpelExtractor(query, + ValueExpressionQueryRewriter.ParsedQuery parsedQuery = createSpelExtractor(query.getQueryString(), parametersShouldBeAccessedByIndex, parameterLabels); String resultingQuery = parsedQuery.getQueryString(); @@ -412,14 +406,14 @@ class StringQuery implements EntityQuery { String match = matcher.group(0); if (JDBC_STYLE_PARAM.matcher(match).find()) { - queryMeta.usesJdbcStyleParameters = true; + jdbcStyle = true; } if (NUMBERED_STYLE_PARAM.matcher(match).find() || NAMED_STYLE_PARAM.matcher(match).find()) { usesJpaStyleParameters = true; } - if (usesJpaStyleParameters && queryMeta.usesJdbcStyleParameters) { + if (usesJpaStyleParameters && jdbcStyle) { throw new IllegalArgumentException("Mixing of ? parameters and other forms like ?1 is not supported"); } @@ -467,7 +461,7 @@ class StringQuery implements EntityQuery { } replacement = targetBinding.hasName() ? ":" + targetBinding.getName() - : ((!usesJpaStyleParameters && queryMeta.usesJdbcStyleParameters) ? "?" + : ((!usesJpaStyleParameters && jdbcStyle) ? "?" : "?" + targetBinding.getPosition()); String result; String substring = matcher.group(2); @@ -484,7 +478,7 @@ class StringQuery implements EntityQuery { resultingQuery = result; } - return resultingQuery; + return new BindableQuery(query, resultingQuery, bindings, jdbcStyle); } private static ValueExpressionQueryRewriter.ParsedQuery createSpelExtractor(String queryWithSpel, @@ -592,9 +586,7 @@ class StringQuery implements EntityQuery { } } - static class Metadata { - private boolean usesJdbcStyleParameters = false; - } + /** * Utility to create unique parameter bindings for LIKE that refer to the same underlying method parameter but are diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StructuredQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StructuredQuery.java new file mode 100644 index 000000000..2ebfcb054 --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StructuredQuery.java @@ -0,0 +1,24 @@ +/* + * Copyright 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; + +/** + * @author Christoph Strobl + */ +public interface StructuredQuery { + + String getQueryString(); +} diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancerUnitTests.java index e0488df11..9a5c9ff30 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancerUnitTests.java @@ -43,7 +43,7 @@ class DefaultQueryEnhancerUnitTests extends QueryEnhancerTckTests { @Test // GH-3546 void shouldApplySorting() { - QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.ofNative("SELECT e FROM Employee e")); + QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.nativeQuery("SELECT e FROM Employee e")); String sql = enhancer.applySorting(Sort.by("foo", "bar")); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQueryUnitTests.java index 8e8528a4b..a23554301 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQueryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQueryUnitTests.java @@ -111,7 +111,7 @@ class ExpressionBasedStringQueryUnitTests { + "AND (n.updatedAt >= ?#{#networkRequest.updatedTime.startDateTime}) AND (n.updatedAt <=?#{#networkRequest.updatedTime.endDateTime})", metadata, CONFIG.getValueExpressionDelegate().getValueExpressionParser(), false, CONFIG.getSelector()); - assertThat(query.isNativeQuery()).isFalse(); + assertThat(query.getDeclaredQuery().isNativeQuery()).isFalse(); } @Test @@ -120,7 +120,7 @@ class ExpressionBasedStringQueryUnitTests { StringQuery query = new ExpressionBasedStringQuery("select n from #{#entityName} n", metadata, CONFIG.getValueExpressionDelegate().getValueExpressionParser(), true, CONFIG.getSelector()); - assertThat(query.isNativeQuery()).isFalse(); + assertThat(query.getDeclaredQuery().isNativeQuery()).isFalse(); } @Test @@ -129,7 +129,7 @@ class ExpressionBasedStringQueryUnitTests { StringQuery query = new ExpressionBasedStringQuery("select u from User u", metadata, CONFIG.getValueExpressionDelegate().getValueExpressionParser(), true, CONFIG.getSelector()); - assertThat(query.isNativeQuery()).isTrue(); + assertThat(query.getDeclaredQuery().isNativeQuery()).isTrue(); } @Test // GH-3041 diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java index 84d83aeb2..6279919b3 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancerUnitTests.java @@ -46,7 +46,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests { @Test // GH-3546 void shouldApplySorting() { - QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.ofJpql("SELECT e FROM Employee e")); + QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.jpqlQuery("SELECT e FROM Employee e")); String sql = enhancer.applySorting(Sort.by("foo", "bar")); @@ -56,7 +56,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests { @Test // GH-3886 void shouldApplySortingWithNullsPrecedence() { - QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.of("SELECT e FROM Employee e", true)); + QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.jpqlQuery("SELECT e FROM Employee e")); String sql = enhancer.rewrite(new DefaultQueryRewriteInformation( Sort.by(Sort.Order.asc("foo").with(Sort.NullHandling.NULLS_LAST), @@ -69,7 +69,7 @@ class JSqlParserQueryEnhancerUnitTests extends QueryEnhancerTckTests { @Test // GH-3707 void countQueriesShouldConsiderPrimaryTableAlias() { - QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.ofNative(""" + QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.nativeQuery(""" SELECT DISTINCT a.*, b.b1 FROM TableA a JOIN TableB b ON a.b = b.b @@ -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).create(stringQuery); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery.getDeclaredQuery()).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).create(stringQuery); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery.getDeclaredQuery()).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).create(stringQuery); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery.getDeclaredQuery()).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).create(stringQuery); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery.getDeclaredQuery()).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).create(stringQuery); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery.getDeclaredQuery()).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).create(stringQuery); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery.getDeclaredQuery()).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).create(stringQuery); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery.getDeclaredQuery()).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).create(stringQuery); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery.getDeclaredQuery()).create(stringQuery); assertThat(queryEnhancer.detectAlias()).isEqualTo(alias); assertThat(QueryUtils.detectAlias(query)).isNull(); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NamedOrIndexedQueryParameterSetterUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NamedOrIndexedQueryParameterSetterUnitTests.java index e85ff114f..d438cdf9a 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NamedOrIndexedQueryParameterSetterUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NamedOrIndexedQueryParameterSetterUnitTests.java @@ -89,7 +89,7 @@ class NamedOrIndexedQueryParameterSetterUnitTests { softly .assertThatThrownBy( - () -> setter.setParameter(BindableQuery.from(query), methodArguments, STRICT)) // + () -> setter.setParameter(QueryParameterSetter.BindableQuery.from(query), methodArguments, STRICT)) // .describedAs("p-type: %s, p-name: %s, p-position: %s, temporal: %s", // parameter.getClass(), // parameter.getName(), // @@ -118,7 +118,7 @@ class NamedOrIndexedQueryParameterSetterUnitTests { softly .assertThatCode( - () -> setter.setParameter(BindableQuery.from(query), methodArguments, LENIENT)) // + () -> setter.setParameter(QueryParameterSetter.BindableQuery.from(query), methodArguments, LENIENT)) // .describedAs("p-type: %s, p-name: %s, p-position: %s, temporal: %s", // parameter.getClass(), // parameter.getName(), // @@ -149,7 +149,7 @@ class NamedOrIndexedQueryParameterSetterUnitTests { temporalType // ); - setter.setParameter(BindableQuery.from(query), methodArguments, LENIENT); + setter.setParameter(QueryParameterSetter.BindableQuery.from(query), methodArguments, LENIENT); if (temporalType == null) { verify(query).setParameter(eq(11), any(Date.class)); @@ -179,7 +179,7 @@ class NamedOrIndexedQueryParameterSetterUnitTests { temporalType // ); - setter.setParameter(BindableQuery.from(query), methodArguments, LENIENT); + setter.setParameter(QueryParameterSetter.BindableQuery.from(query), methodArguments, LENIENT); if (temporalType == null) { verify(query, never()).setParameter(anyInt(), any(Date.class)); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactoryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactoryUnitTests.java index 7456e047c..aaccc4cad 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactoryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactoryUnitTests.java @@ -34,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).create(query); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(query.getDeclaredQuery()).create(query); assertThat(queryEnhancer) // .isInstanceOf(JpaQueryEnhancer.class); @@ -49,7 +49,7 @@ class QueryEnhancerFactoryUnitTests { StringQuery query = new StringQuery("select * from User", true); - QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(query).create(query); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(query.getDeclaredQuery()).create(query); assertThat(queryEnhancer) // .isInstanceOf(JSqlParserQueryEnhancer.class); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java index 4b4bb8dfe..7a0f4e178 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerTckTests.java @@ -35,7 +35,7 @@ abstract class QueryEnhancerTckTests { @MethodSource("nativeCountQueries") // GH-2773 void shouldDeriveNativeCountQuery(String query, String expected) { - QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.ofNative(query)); + QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.nativeQuery(query)); String countQueryFor = enhancer.createCountQueryFor(); // lenient cleanup to allow for rendering variance @@ -119,7 +119,7 @@ abstract class QueryEnhancerTckTests { @MethodSource("jpqlCountQueries") void shouldDeriveJpqlCountQuery(String query, String expected) { - QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.ofJpql(query)); + QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.jpqlQuery(query)); String countQueryFor = enhancer.createCountQueryFor(null); assertThat(countQueryFor).isEqualToIgnoringCase(expected); @@ -178,7 +178,7 @@ abstract class QueryEnhancerTckTests { @MethodSource("nativeQueriesWithVariables") void shouldDeriveNativeCountQueryWithVariable(String query, String expected) { - QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.ofNative(query)); + QueryEnhancer enhancer = createQueryEnhancer(DeclaredQuery.nativeQuery(query)); String countQueryFor = enhancer.createCountQueryFor(); assertThat(countQueryFor).isEqualToIgnoringCase(expected); @@ -205,7 +205,7 @@ abstract class QueryEnhancerTckTests { StringQuery query = new StringQuery("select x, frommage, y from t", true); - assertThat(createQueryEnhancer(query).getProjection()).isEqualTo("x, frommage, y"); + assertThat(createQueryEnhancer(query.getDeclaredQuery()).getProjection()).isEqualTo("x, frommage, y"); } abstract QueryEnhancer createQueryEnhancer(DeclaredQuery query); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java index 66dbcca20..0e5f44cd8 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerUnitTests.java @@ -632,7 +632,7 @@ class QueryEnhancerUnitTests { assertThat(modiQuery.hasConstructorExpression()).isEqualTo(constructorExpressionNotConsideringQueryType); assertThat(countQueryForNotConsiderQueryType).isEqualToIgnoringCase(modifyingQuery); - assertThat(QueryEnhancerFactory.forQuery(modiQuery).create(modiQuery).createCountQueryFor()) + assertThat(QueryEnhancerFactory.forQuery(modiQuery.getDeclaredQuery()).create(modiQuery.getDeclaredQuery()).createCountQueryFor()) .isEqualToIgnoringCase(modifyingQuery); } @@ -641,7 +641,7 @@ class QueryEnhancerUnitTests { void insertStatementIsProcessedSameAsDefault(String insertQuery) { StringQuery stringQuery = new StringQuery(insertQuery, true); - QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery).create(stringQuery); + QueryEnhancer queryEnhancer = QueryEnhancerFactory.forQuery(stringQuery.getDeclaredQuery()).create(stringQuery.getDeclaredQuery()); Sort sorting = Sort.by("day").descending(); @@ -697,7 +697,7 @@ class QueryEnhancerUnitTests { } private static QueryEnhancer getEnhancer(IntrospectedQuery query) { - return QueryEnhancerFactory.forQuery(query).create(query); + return QueryEnhancerFactory.forQuery(query.getDeclaredQuery()).create(query.getDeclaredQuery()); } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java index 4fa844108..155b291a5 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/StringQueryUnitTests.java @@ -17,7 +17,6 @@ package org.springframework.data.jpa.repository.query; import static org.assertj.core.api.Assertions.*; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -29,6 +28,7 @@ import org.springframework.data.jpa.repository.query.ParameterBinding.InParamete import org.springframework.data.jpa.repository.query.ParameterBinding.LikeParameterBinding; import org.springframework.data.jpa.repository.query.ParameterBinding.MethodInvocationArgument; import org.springframework.data.jpa.repository.query.ParameterBinding.ParameterOrigin; +import org.springframework.data.jpa.repository.query.StringQuery.ParameterBindingParser; import org.springframework.data.repository.query.parser.Part.Type; /** @@ -926,11 +926,10 @@ class StringQueryUnitTests { private void checkHasNamedParameter(String query, boolean expected, String label) { - List bindings = new ArrayList<>(); - StringQuery.ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query, - bindings, new StringQuery.Metadata()); + DeclaredQuery source = nativeQuery ? DeclaredQuery.nativeQuery(query) : DeclaredQuery.jpqlQuery(query); + BindableQuery bindableQuery = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(source); - assertThat(bindings.stream().anyMatch(it -> it.getIdentifier().hasName())) // + assertThat(bindableQuery.getBindings().stream().anyMatch(it -> it.getIdentifier().hasName())) // .describedAs(String.format("<%s> (%s)", query, label)) // .isEqualTo(expected); }