From c811561a83b46c5c2ee82dbdc2fbc236d56de829 Mon Sep 17 00:00:00 2001 From: academey Date: Wed, 20 Aug 2025 23:49:15 +0900 Subject: [PATCH] Fix #{#entityName} SpEL expression in native queries to return table name Fixes #3979 Native queries with SELECT * and #{#entityName} were failing because #{#entityName} was returning the entity name instead of the table name for native queries. This change: - Adds getTableName() method to JpaEntityMetadata interface with default implementation - Makes #{#entityName} return table name for native queries and entity name for JPQL queries - Removes instanceof checks for cleaner, type-safe code Added test to verify the fix works correctly. Signed-off-by: academey --- .../query/DefaultJpaEntityMetadata.java | 8 ++++++++ .../repository/query/JpaEntityMetadata.java | 10 ++++++++++ .../jpa/repository/query/TemplatedQuery.java | 18 ++++++++++++++++-- .../jpa/repository/UserRepositoryTests.java | 8 ++++++++ .../jpa/repository/sample/UserRepository.java | 4 ++++ 5 files changed, 46 insertions(+), 2 deletions(-) diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultJpaEntityMetadata.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultJpaEntityMetadata.java index daa30cbb87..e42168aea5 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultJpaEntityMetadata.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultJpaEntityMetadata.java @@ -16,6 +16,7 @@ package org.springframework.data.jpa.repository.query; import jakarta.persistence.Entity; +import jakarta.persistence.Table; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.util.Assert; @@ -53,4 +54,11 @@ public String getEntityName() { Entity entity = AnnotatedElementUtils.findMergedAnnotation(domainType, Entity.class); return null != entity && StringUtils.hasText(entity.name()) ? entity.name() : domainType.getSimpleName(); } + + @Override + public String getTableName() { + + Table table = AnnotatedElementUtils.findMergedAnnotation(domainType, Table.class); + return null != table && StringUtils.hasText(table.name()) ? table.name() : getEntityName(); + } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityMetadata.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityMetadata.java index f7cc6cc9a0..9aba3ddb44 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityMetadata.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityMetadata.java @@ -30,4 +30,14 @@ public interface JpaEntityMetadata extends EntityMetadata { * @return */ String getEntityName(); + + /** + * Returns the table name from the @Table annotation or defaults to the entity name. + * + * @return the table name + * @since 4.0 + */ + default String getTableName() { + return getEntityName(); + } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/TemplatedQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/TemplatedQuery.java index 487a7b11f8..5958e7de2c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/TemplatedQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/TemplatedQuery.java @@ -86,7 +86,7 @@ public static EntityQuery create(DeclaredQuery declaredQuery, JpaEntityMetadata< ValueExpressionParser expressionParser = queryContext.getValueExpressionDelegate().getValueExpressionParser(); String resolvedExpressionQuery = renderQueryIfExpressionOrReturnQuery(declaredQuery.getQueryString(), - entityMetadata, expressionParser); + entityMetadata, expressionParser, declaredQuery.isNative()); return EntityQuery.create(declaredQuery.rewrite(resolvedExpressionQuery), queryContext.getSelector()); } @@ -98,6 +98,17 @@ public static EntityQuery create(DeclaredQuery declaredQuery, JpaEntityMetadata< */ static String renderQueryIfExpressionOrReturnQuery(String query, JpaEntityMetadata metadata, ValueExpressionParser parser) { + return renderQueryIfExpressionOrReturnQuery(query, metadata, parser, false); + } + + /** + * @param query, the query expression potentially containing a SpEL expression. Must not be {@literal null}. + * @param metadata the {@link JpaEntityMetadata} for the given entity. Must not be {@literal null}. + * @param parser Must not be {@literal null}. + * @param isNative whether the query is a native SQL query. + */ + static String renderQueryIfExpressionOrReturnQuery(String query, JpaEntityMetadata metadata, + ValueExpressionParser parser, boolean isNative) { Assert.notNull(query, "query must not be null"); Assert.notNull(metadata, "metadata must not be null"); @@ -108,7 +119,10 @@ static String renderQueryIfExpressionOrReturnQuery(String query, JpaEntityMetada } SimpleEvaluationContext evalContext = SimpleEvaluationContext.forReadOnlyDataBinding().build(); - evalContext.setVariable(ENTITY_NAME, metadata.getEntityName()); + + String entityNameValue = isNative ? metadata.getTableName() : metadata.getEntityName(); + + evalContext.setVariable(ENTITY_NAME, entityNameValue); query = potentiallyQuoteExpressionsParameter(query); 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 8980836d8d..e6a9a9f6c5 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 @@ -783,6 +783,14 @@ void executesNativeQueryCorrectly() { assertThat(repository.findNativeByLastname("Matthews")).containsOnly(thirdUser); } + @Test // GH-3979 + void executesNativeQueryWithSelectStarCorrectly() { + + flushTestUsers(); + + assertThat(repository.findNativeWithSelectStar("Matthews")).containsOnly(thirdUser); + } + @Test // DATAJPA-132 void executesFinderWithTrueKeywordCorrectly() { 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 161bb33a28..d4e9987789 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 @@ -676,6 +676,10 @@ List findAllAndSortByFunctionResultNamedParameter(@Param("namedParameter @Query(value = "VALUES (1)", nativeQuery = true) List valuesStatementNative(); + // GH-3979 + @Query(value = "select * from #{#entityName} where lastname = ?1", nativeQuery = true) + List findNativeWithSelectStar(String lastname); + // GH-2578 @Query(value = "with sample_data as ( Select * from SD_User u where u.age > 30 ) \n select * from sample_data", nativeQuery = true)