From 7b9baa5e3407829efee359aa66bad49bcf000dce Mon Sep 17 00:00:00 2001 From: "Greg L. Turnquist" Date: Fri, 11 Aug 2023 10:20:32 -0500 Subject: [PATCH 1/3] New branch. --- pom.xml | 2 +- spring-data-envers/pom.xml | 4 ++-- spring-data-jpa-distribution/pom.xml | 2 +- spring-data-jpa/pom.xml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index b709f1f03a..929feda02c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-jpa-parent - 3.2.0-SNAPSHOT + 3.2.0-gh-3096-SNAPSHOT pom Spring Data JPA Parent diff --git a/spring-data-envers/pom.xml b/spring-data-envers/pom.xml index f239d6394b..8e0e5ba1b8 100755 --- a/spring-data-envers/pom.xml +++ b/spring-data-envers/pom.xml @@ -5,12 +5,12 @@ org.springframework.data spring-data-envers - 3.2.0-SNAPSHOT + 3.2.0-gh-3096-SNAPSHOT org.springframework.data spring-data-jpa-parent - 3.2.0-SNAPSHOT + 3.2.0-gh-3096-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa-distribution/pom.xml b/spring-data-jpa-distribution/pom.xml index 95ed749af3..d1bd752d77 100644 --- a/spring-data-jpa-distribution/pom.xml +++ b/spring-data-jpa-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-jpa-parent - 3.2.0-SNAPSHOT + 3.2.0-gh-3096-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml index b21b03c313..3087e3b8e8 100644 --- a/spring-data-jpa/pom.xml +++ b/spring-data-jpa/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-jpa - 3.2.0-SNAPSHOT + 3.2.0-gh-3096-SNAPSHOT Spring Data JPA Spring Data module for JPA repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-jpa-parent - 3.2.0-SNAPSHOT + 3.2.0-gh-3096-SNAPSHOT ../pom.xml From d88060eb6d6b1a60b2dff621275622a3ba54dc66 Mon Sep 17 00:00:00 2001 From: "Greg L. Turnquist" Date: Fri, 11 Aug 2023 12:17:20 -0500 Subject: [PATCH 2/3] Properly handle native queries with SpEL expressions. Native queries with SpEL expressions should be properly parsed instead of converted to non-native queries. See #3096 --- .../jpa/repository/query/DeclaredQuery.java | 24 ++++++++++++++++++- .../query/ExpressionBasedStringQuery.java | 7 ++++-- .../jpa/repository/query/JpaQueryFactory.java | 3 ++- .../jpa/repository/query/JpaQueryMethod.java | 2 +- .../jpa/repository/UserRepositoryTests.java | 8 +++++++ .../ExpressionBasedStringQueryUnitTests.java | 8 +++---- .../jpa/repository/sample/UserRepository.java | 4 ++++ 7 files changed, 47 insertions(+), 9 deletions(-) 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 670e030fb2..58538860f1 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 @@ -25,6 +25,7 @@ * * @author Jens Schauder * @author Diego Krupitza + * @author Greg Turnquist * @since 2.0.3 */ interface DeclaredQuery { @@ -37,7 +38,28 @@ interface DeclaredQuery { * @return a {@literal DeclaredQuery} instance even for a {@literal null} or empty argument. */ static DeclaredQuery of(@Nullable String query, boolean nativeQuery) { - return ObjectUtils.isEmpty(query) ? EmptyDeclaredQuery.EMPTY_QUERY : new StringQuery(query, nativeQuery); + return of(query, nativeQuery, null); + } + + /** + * Creates a {@literal DeclaredQuery} from a query {@literal String} and the related {@link JpaQueryMethod}. + * + * @param query might be {@literal null} or empty. + * @param nativeQuery is a given query is native or not + * @param method is a {@link JpaQueryMethod} that has the related metadata + * @return a {@literal DeclaredQuery} instance even for a {@literal null} or empty argument. + */ + static DeclaredQuery of(@Nullable String query, Boolean nativeQuery, @Nullable JpaQueryMethod method) { + + if (ObjectUtils.isEmpty(query)) { + return EmptyDeclaredQuery.EMPTY_QUERY; + } + + if (ExpressionBasedStringQuery.containsExpression(query) && method != null) { + return new ExpressionBasedStringQuery(query, method.getEntityInformation(), JpaQueryFactory.PARSER, nativeQuery); + } + + return new StringQuery(query, nativeQuery); } /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java index 3bee9c2d48..6ee861dfdf 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java @@ -61,7 +61,7 @@ class ExpressionBasedStringQuery extends StringQuery { */ public ExpressionBasedStringQuery(String query, JpaEntityMetadata metadata, SpelExpressionParser parser, boolean nativeQuery) { - super(renderQueryIfExpressionOrReturnQuery(query, metadata, parser), nativeQuery && !containsExpression(query)); + super(renderQueryIfExpressionOrReturnQuery(query, metadata, parser), nativeQuery); } /** @@ -118,7 +118,10 @@ private static String potentiallyQuoteExpressionsParameter(String query) { return EXPRESSION_PARAMETER_QUOTING.matcher(query).replaceAll(QUOTED_EXPRESSION_PARAMETER); } - private static boolean containsExpression(String query) { + /** + * Does the {@literal query} contains a SpEL expression? + */ + static boolean containsExpression(String query) { return query.contains(ENTITY_NAME_VARIABLE_EXPRESSION); } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java index ef95696cb6..d9088aef41 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java @@ -29,12 +29,13 @@ * * @author Thomas Darimont * @author Mark Paluch + * @author Greg Turnquist */ enum JpaQueryFactory { INSTANCE; - private static final SpelExpressionParser PARSER = new SpelExpressionParser(); + static final SpelExpressionParser PARSER = new SpelExpressionParser(); /** * Creates a {@link RepositoryQuery} from the given {@link String} query. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java index ed566ba52c..ea4ecf4551 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java @@ -162,7 +162,7 @@ private void assertParameterNamesInAnnotatedQuery() { String annotatedQuery = getAnnotatedQuery(); - if (!DeclaredQuery.of(annotatedQuery, this.isNativeQuery.get()).hasNamedParameter()) { + if (!DeclaredQuery.of(annotatedQuery, this.isNativeQuery.get(), this).hasNamedParameter()) { return; } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java index 9264185849..eb5d45f516 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java @@ -1073,6 +1073,14 @@ void shouldSupportModifyingQueryWithVarArgs() { assertThat(repository.findByActiveTrue()).isEmpty(); } + @Test // GH-3096 + void shouldSupportNativeQueriesWithSpEL() { + + flushTestUsers(); + + assertThat(repository.nativeQueryWithSpEL()).containsExactly(firstUser, secondUser, thirdUser, fourthUser); + } + @Test // DATAJPA-405 void executesFinderWithOrderClauseOnly() { 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 af1928d506..80f0b1d72d 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 @@ -97,7 +97,7 @@ void shouldDetectBindParameterCountCorrectlyWithJDBCStyleParameters() { } @Test - void shouldDetectComplexNativeQueriesWithSpelAsNonNative() { + void shouldDetectComplexNativeQueriesWithSpelAsRetainingNativeQueryStatus() { StringQuery query = new ExpressionBasedStringQuery( "select n from #{#entityName} n where (LOWER(n.name) LIKE LOWER(NULLIF(text(concat('%',?#{#networkRequest.name},'%')), '')) OR ?#{#networkRequest.name} IS NULL )" @@ -106,15 +106,15 @@ void shouldDetectComplexNativeQueriesWithSpelAsNonNative() { + "AND (n.updatedAt >= ?#{#networkRequest.updatedTime.startDateTime}) AND (n.updatedAt <=?#{#networkRequest.updatedTime.endDateTime})", metadata, SPEL_PARSER, true); - assertThat(query.isNativeQuery()).isFalse(); + assertThat(query.isNativeQuery()).isTrue(); } @Test - void shouldDetectSimpleNativeQueriesWithSpelAsNonNative() { + void shouldDetectSimpleNativeQueriesWithSpelAsRetainingNativeQueryStatus() { StringQuery query = new ExpressionBasedStringQuery("select n from #{#entityName} n", metadata, SPEL_PARSER, true); - assertThat(query.isNativeQuery()).isFalse(); + assertThat(query.isNativeQuery()).isTrue(); } @Test diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index c9a342538f..734979ecdd 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -734,6 +734,10 @@ List findAllAndSortByFunctionResultNamedParameter(@Param("namedParameter @Query("select u from User u where u.firstname >= (select Min(u0.firstname) from User u0)") List findProjectionBySubselect(); + // GH-3096 + @Query(value = "select * from SD_User as #{#entityName}", nativeQuery = true) + List nativeQueryWithSpEL(); + Window findBy(OffsetScrollPosition position); interface RolesAndFirstname { From 0c34d4d7d5a382661a9206f61076857a2507f608 Mon Sep 17 00:00:00 2001 From: "Greg L. Turnquist" Date: Wed, 6 Sep 2023 15:29:00 -0500 Subject: [PATCH 3/3] Switch to JpaEntityMetadata --- .../data/jpa/repository/query/DeclaredQuery.java | 8 ++++---- .../data/jpa/repository/query/JpaQueryMethod.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) 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 58538860f1..f4e048df9b 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 @@ -46,17 +46,17 @@ static DeclaredQuery of(@Nullable String query, boolean nativeQuery) { * * @param query might be {@literal null} or empty. * @param nativeQuery is a given query is native or not - * @param method is a {@link JpaQueryMethod} that has the related metadata + * @param entityMetadata is a {@link JpaEntityMetadata} that might be {@literal null} * @return a {@literal DeclaredQuery} instance even for a {@literal null} or empty argument. */ - static DeclaredQuery of(@Nullable String query, Boolean nativeQuery, @Nullable JpaQueryMethod method) { + static DeclaredQuery of(@Nullable String query, Boolean nativeQuery, @Nullable JpaEntityMetadata entityMetadata) { if (ObjectUtils.isEmpty(query)) { return EmptyDeclaredQuery.EMPTY_QUERY; } - if (ExpressionBasedStringQuery.containsExpression(query) && method != null) { - return new ExpressionBasedStringQuery(query, method.getEntityInformation(), JpaQueryFactory.PARSER, nativeQuery); + if (ExpressionBasedStringQuery.containsExpression(query) && entityMetadata != null) { + return new ExpressionBasedStringQuery(query, entityMetadata, JpaQueryFactory.PARSER, nativeQuery); } return new StringQuery(query, nativeQuery); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java index ea4ecf4551..888dfc3cee 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java @@ -162,7 +162,7 @@ private void assertParameterNamesInAnnotatedQuery() { String annotatedQuery = getAnnotatedQuery(); - if (!DeclaredQuery.of(annotatedQuery, this.isNativeQuery.get(), this).hasNamedParameter()) { + if (!DeclaredQuery.of(annotatedQuery, this.isNativeQuery.get(), getEntityInformation()).hasNamedParameter()) { return; }