diff --git a/pom.xml b/pom.xml index 6fd19eccb9..4e260500de 100755 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-jpa-parent - 4.0.0-SNAPSHOT + 4.0.x-GH-4029-SNAPSHOT pom Spring Data JPA Parent diff --git a/spring-data-envers/pom.xml b/spring-data-envers/pom.xml index 0bdf2c8e7e..124d4a4271 100755 --- a/spring-data-envers/pom.xml +++ b/spring-data-envers/pom.xml @@ -5,12 +5,12 @@ org.springframework.data spring-data-envers - 4.0.0-SNAPSHOT + 4.0.x-GH-4029-SNAPSHOT org.springframework.data spring-data-jpa-parent - 4.0.0-SNAPSHOT + 4.0.x-GH-4029-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa-distribution/pom.xml b/spring-data-jpa-distribution/pom.xml index af5244a230..1981be4b34 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 - 4.0.0-SNAPSHOT + 4.0.x-GH-4029-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml index cbec8a2645..5903a7e834 100644 --- a/spring-data-jpa/pom.xml +++ b/spring-data-jpa/pom.xml @@ -7,7 +7,7 @@ org.springframework.data spring-data-jpa - 4.0.0-SNAPSHOT + 4.0.x-GH-4029-SNAPSHOT Spring Data JPA Spring Data module for JPA repositories. @@ -16,7 +16,7 @@ org.springframework.data spring-data-jpa-parent - 4.0.0-SNAPSHOT + 4.0.x-GH-4029-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/QueriesFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/QueriesFactory.java index 3d5bee6bf9..bf43d72f68 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/QueriesFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/QueriesFactory.java @@ -57,6 +57,7 @@ * Factory for {@link AotQueries}. * * @author Mark Paluch + * @author Christoph Strobl * @since 4.0 */ class QueriesFactory { @@ -123,7 +124,7 @@ public AotQueries createQueries(RepositoryInformation repositoryInformation, Ret QueryEnhancerSelector selector, MergedAnnotation query, JpaQueryMethod queryMethod) { if (query.isPresent() && StringUtils.hasText(query.getString("value"))) { - return buildStringQuery(repositoryInformation.getDomainType(), returnedType, selector, query, queryMethod); + return buildStringQuery(returnedType, selector, query, queryMethod); } String queryName = queryMethod.getNamedQueryName(); @@ -138,10 +139,10 @@ private boolean hasNamedQuery(ReturnedType returnedType, String queryName) { return namedQueries.hasQuery(queryName) || getNamedQuery(returnedType, queryName) != null; } - private AotQueries buildStringQuery(Class domainType, ReturnedType returnedType, QueryEnhancerSelector selector, + private AotQueries buildStringQuery(ReturnedType returnedType, QueryEnhancerSelector selector, MergedAnnotation query, JpaQueryMethod queryMethod) { - UnaryOperator operator = s -> s.replaceAll("#\\{#entityName}", domainType.getSimpleName()); + UnaryOperator operator = s -> s.replaceAll("#\\{#entityName}", queryMethod.getEntityInformation().getEntityName()); boolean isNative = query.getBoolean("nativeQuery"); Function queryFunction = isNative ? DeclaredQuery::nativeQuery : DeclaredQuery::jpqlQuery; queryFunction = operator.andThen(queryFunction); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/aot/QueriesFactoryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/aot/QueriesFactoryUnitTests.java new file mode 100644 index 0000000000..dbd4dc472f --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/aot/QueriesFactoryUnitTests.java @@ -0,0 +1,83 @@ +/* + * Copyright 2025-present 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.aot; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import jakarta.persistence.EntityManagerFactory; + +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.jpa.repository.query.JpaEntityMetadata; +import org.springframework.data.jpa.repository.query.JpaQueryMethod; +import org.springframework.data.jpa.repository.query.QueryEnhancerSelector; +import org.springframework.data.jpa.repository.support.JpaEntityInformation; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; +import org.springframework.data.repository.config.RepositoryConfigurationSource; +import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.repository.query.ReturnedType; + +/** + * Unit tests for {@link QueriesFactory}. + * + * @author Christoph Strobl + */ +class QueriesFactoryUnitTests { + + QueriesFactory factory; + + @BeforeEach + void setUp() { + + RepositoryConfigurationSource configSource = Mockito.mock(RepositoryConfigurationSource.class); + EntityManagerFactory entityManagerFactory = Mockito.mock(EntityManagerFactory.class); + + factory = new QueriesFactory(configSource, entityManagerFactory, this.getClass().getClassLoader()); + } + + @Test // GH-4029 + @SuppressWarnings({ "rawtypes", "unchecked" }) + void stringQueryShouldResolveEntityNameFromJakartaAnnotationIfPresent() { + + RepositoryInformation repositoryInformation = Mockito.mock(RepositoryInformation.class); + JpaEntityMetadata entityMetadata = Mockito.mock(JpaEntityInformation.class); + when(entityMetadata.getEntityName()).thenReturn("CustomNamed"); + + MergedAnnotation queryAnnotation = Mockito.mock(MergedAnnotation.class); + when(queryAnnotation.isPresent()).thenReturn(true); + when(queryAnnotation.getString(eq("value"))).thenReturn("select t from #{#entityName} t"); + when(queryAnnotation.getBoolean(eq("nativeQuery"))).thenReturn(false); + when(queryAnnotation.getString("countQuery")).thenReturn("select count(t) from #{#entityName} t"); + + JpaQueryMethod queryMethod = Mockito.mock(JpaQueryMethod.class); + when(queryMethod.getEntityInformation()).thenReturn((JpaEntityMetadata) entityMetadata); + + AotQueries generatedQueries = factory.createQueries(repositoryInformation, + ReturnedType.of(Object.class, Object.class, new SpelAwareProxyProjectionFactory()), + QueryEnhancerSelector.DEFAULT_SELECTOR, queryAnnotation, queryMethod); + + assertThat(generatedQueries.result()).asInstanceOf(InstanceOfAssertFactories.type(StringAotQuery.class)) + .extracting(StringAotQuery::getQueryString).isEqualTo("select t from CustomNamed t"); + assertThat(generatedQueries.count()).asInstanceOf(InstanceOfAssertFactories.type(StringAotQuery.class)) + .extracting(StringAotQuery::getQueryString).isEqualTo("select count(t) from CustomNamed t"); + } +}