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..d472c06f2b 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 @@ -17,6 +17,7 @@ import java.util.List; +import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; @@ -37,7 +38,30 @@ 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, null); + } + + /** + * Creates a {@literal DeclaredQuery} from a query {@literal String}. + * + * @param query might be {@literal null} or empty. + * @param nativeQuery is a given query is native or not + * @param entityMetadata metadata about the repository's related type + * @param parser a {@link SpelExpressionParser} used to process potential SpEL expressions found in the query + * @return a {@literal DeclaredQuery} instance even for a {@literal null} or empty argument. + */ + static DeclaredQuery of(@Nullable String query, boolean nativeQuery, @Nullable JpaEntityMetadata entityMetadata, + @Nullable SpelExpressionParser parser) { + + if (ObjectUtils.isEmpty(query)) { + return EmptyDeclaredQuery.EMPTY_QUERY; + } + + if (entityMetadata != null) { + return new ExpressionBasedStringQuery(query, entityMetadata, 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..3a71bcdff8 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); } /** 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..84c0edd628 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 @@ -34,7 +34,7 @@ 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..4d838fafd0 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.getEntityInformation(), JpaQueryFactory.PARSER).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..dfea221678 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 @@ -106,7 +106,7 @@ 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 @@ -114,7 +114,7 @@ void shouldDetectSimpleNativeQueriesWithSpelAsNonNative() { 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 {