From 1529ec252a6697a19418af41ca3a7a7145f1ee7c Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 19 Mar 2025 10:06:27 +0100 Subject: [PATCH] Apply QueryRewriter to count queries as well. We now use QueryRewriter to post-process count queries as well. Previously, only the actual result query has been processed. Closes #3801 --- .../query/AbstractStringBasedJpaQuery.java | 1 - .../query/JpaQueryLookupStrategy.java | 2 +- .../data/jpa/repository/query/NamedQuery.java | 17 ++++++++++------- .../repository/query/NamedQueryUnitTests.java | 9 +++++++-- .../modules/ROOT/pages/jpa/query-methods.adoc | 2 ++ 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java index 148567a9e..61d5ea7f3 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java @@ -112,7 +112,6 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery { }); this.countParameterBinder = Lazy.of(() -> this.createBinder(this.countQuery.get())); - this.queryRewriter = queryConfiguration.getQueryRewriter(method); JpaParameters parameters = method.getParameters(); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java index 719e838fe..37f2e27d2 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java @@ -169,7 +169,7 @@ public final class JpaQueryLookupStrategy { configuration); } - RepositoryQuery query = NamedQuery.lookupFrom(method, em, configuration.getSelector()); + RepositoryQuery query = NamedQuery.lookupFrom(method, em, configuration); return query != null ? query : NO_QUERY; } 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 de26c392b..5bf986d4b 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 @@ -56,11 +56,12 @@ final class NamedQuery extends AbstractJpaQuery { private final @Nullable String countProjection; private final boolean namedCountQueryIsPresent; private final Lazy entityQuery; + private final QueryRewriter queryRewriter; /** * Creates a new {@link NamedQuery}. */ - private NamedQuery(JpaQueryMethod method, EntityManager em, QueryEnhancerSelector selector, QueryRewriter queryRewriter) { + private NamedQuery(JpaQueryMethod method, EntityManager em, JpaQueryConfiguration queryConfiguration) { super(method, em); @@ -68,7 +69,7 @@ final class NamedQuery extends AbstractJpaQuery { this.countQueryName = method.getNamedCountQueryName(); QueryExtractor extractor = method.getQueryExtractor(); this.countProjection = method.getCountQueryProjection(); - this.queryRewriter = queryRewriter; + this.queryRewriter = queryConfiguration.getQueryRewriter(method); Parameters parameters = method.getParameters(); @@ -104,7 +105,7 @@ final class NamedQuery extends AbstractJpaQuery { declaredQuery = DeclaredQuery.jpqlQuery(queryString); } - this.entityQuery = Lazy.of(() -> EntityQuery.create(declaredQuery, selector)); + this.entityQuery = Lazy.of(() -> EntityQuery.create(declaredQuery, queryConfiguration.getSelector())); } /** @@ -138,9 +139,10 @@ final class NamedQuery extends AbstractJpaQuery { * @param method must not be {@literal null}. * @param em must not be {@literal null}. * @param selector must not be {@literal null}. + * @param queryConfiguration must not be {@literal null}. */ public static @Nullable RepositoryQuery lookupFrom(JpaQueryMethod method, EntityManager em, - QueryEnhancerSelector selector) { + JpaQueryConfiguration queryConfiguration) { String queryName = method.getNamedQueryName(); @@ -158,7 +160,7 @@ final class NamedQuery extends AbstractJpaQuery { method.isNativeQuery() ? "NativeQuery" : "Query")); } - RepositoryQuery query = new NamedQuery(method, em, selector); + RepositoryQuery query = new NamedQuery(method, em, queryConfiguration); if (LOG.isDebugEnabled()) { LOG.debug(String.format("Found named query '%s'", queryName)); } @@ -193,6 +195,7 @@ final class NamedQuery extends AbstractJpaQuery { } else { String countQueryString = entityQuery.get().deriveCountQuery(countProjection).getQueryString(); + countQueryString = potentiallyRewriteQuery(countQueryString, accessor.getSort(), accessor.getPageable()); countQuery = em.createQuery(countQueryString, Long.class); } @@ -235,9 +238,9 @@ final class NamedQuery extends AbstractJpaQuery { * @param pageable * @return */ - private String potentiallyRewriteQuery(String originalQuery, Sort sort, Pageable pageable) { + private String potentiallyRewriteQuery(String originalQuery, Sort sort, @Nullable Pageable pageable) { - return pageable.isPaged() // + return pageable != null && pageable.isPaged() // ? queryRewriter.rewrite(originalQuery, pageable) // : queryRewriter.rewrite(originalQuery, sort); } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NamedQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NamedQueryUnitTests.java index 79df5c519..71bd266f0 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NamedQueryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/NamedQueryUnitTests.java @@ -41,6 +41,7 @@ import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.query.QueryCreationException; +import org.springframework.data.repository.query.ValueExpressionDelegate; import org.springframework.data.util.TypeInformation; /** @@ -55,6 +56,9 @@ import org.springframework.data.util.TypeInformation; @MockitoSettings(strictness = Strictness.LENIENT) class NamedQueryUnitTests { + private static final JpaQueryConfiguration CONFIG = new JpaQueryConfiguration(QueryRewriterProvider.simple(), + QueryEnhancerSelector.DEFAULT_SELECTOR, ValueExpressionDelegate.create(), EscapeCharacter.DEFAULT); + @Mock RepositoryMetadata metadata; @Mock QueryExtractor extractor; @Mock EntityManager em; @@ -89,7 +93,8 @@ 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, QueryEnhancerSelector.DEFAULT_SELECTOR, QueryRewriter.IdentityQueryRewriter.INSTANCE)); + assertThatExceptionOfType(QueryCreationException.class) + .isThrownBy(() -> NamedQuery.lookupFrom(queryMethod, em, CONFIG)); } @Test // DATAJPA-142 @@ -101,7 +106,7 @@ class NamedQueryUnitTests { TypedQuery countQuery = mock(TypedQuery.class); when(em.createNamedQuery(eq(queryMethod.getNamedCountQueryName()), eq(Long.class))).thenReturn(countQuery); - NamedQuery query = (NamedQuery) NamedQuery.lookupFrom(queryMethod, em, QueryEnhancerSelector.DEFAULT_SELECTOR, QueryRewriter.IdentityQueryRewriter.INSTANCE); + NamedQuery query = (NamedQuery) NamedQuery.lookupFrom(queryMethod, em, CONFIG); query.doCreateCountQuery(new JpaParametersParameterAccessor(queryMethod.getParameters(), new Object[1])); verify(em, times(1)).createNamedQuery(queryMethod.getNamedCountQueryName(), Long.class); diff --git a/src/main/antora/modules/ROOT/pages/jpa/query-methods.adoc b/src/main/antora/modules/ROOT/pages/jpa/query-methods.adoc index 52b3e8a2c..eaa05b0b3 100644 --- a/src/main/antora/modules/ROOT/pages/jpa/query-methods.adoc +++ b/src/main/antora/modules/ROOT/pages/jpa/query-methods.adoc @@ -376,6 +376,8 @@ Sometimes, no matter how many features you try to apply, it seems impossible to You have the ability to get your hands on the query, right before it's sent to the `EntityManager` and "rewrite" it. That is, you can make any alterations at the last moment. +Query rewriting applies to the actual query and, when applicable, to count queries. +Count queries are optimized and therefore, either not necessary or a count is obtained through other means, such as derived from a Hibernate `SelectionQuery`. .Declare a QueryRewriter using `@Query` ====